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.
Files changed (69) hide show
  1. package/README.md +102 -13
  2. package/dist/src/constants.d.ts +12 -0
  3. package/dist/src/constants.d.ts.map +1 -1
  4. package/dist/src/constants.js +13 -0
  5. package/dist/src/constants.js.map +1 -1
  6. package/dist/src/plugin/cache/index.d.ts +5 -0
  7. package/dist/src/plugin/cache/index.d.ts.map +1 -0
  8. package/dist/src/plugin/cache/index.js +5 -0
  9. package/dist/src/plugin/cache/index.js.map +1 -0
  10. package/dist/src/plugin/cache/signature-cache.d.ts +87 -0
  11. package/dist/src/plugin/cache/signature-cache.d.ts.map +1 -0
  12. package/dist/src/plugin/cache/signature-cache.js +325 -0
  13. package/dist/src/plugin/cache/signature-cache.js.map +1 -0
  14. package/dist/src/plugin/cache.d.ts +16 -0
  15. package/dist/src/plugin/cache.d.ts.map +1 -1
  16. package/dist/src/plugin/cache.js +85 -23
  17. package/dist/src/plugin/cache.js.map +1 -1
  18. package/dist/src/plugin/config/index.d.ts +16 -0
  19. package/dist/src/plugin/config/index.d.ts.map +1 -0
  20. package/dist/src/plugin/config/index.js +16 -0
  21. package/dist/src/plugin/config/index.js.map +1 -0
  22. package/dist/src/plugin/config/loader.d.ts +35 -0
  23. package/dist/src/plugin/config/loader.d.ts.map +1 -0
  24. package/dist/src/plugin/config/loader.js +173 -0
  25. package/dist/src/plugin/config/loader.js.map +1 -0
  26. package/dist/src/plugin/config/schema.d.ts +162 -0
  27. package/dist/src/plugin/config/schema.d.ts.map +1 -0
  28. package/dist/src/plugin/config/schema.js +128 -0
  29. package/dist/src/plugin/config/schema.js.map +1 -0
  30. package/dist/src/plugin/debug.d.ts +8 -2
  31. package/dist/src/plugin/debug.d.ts.map +1 -1
  32. package/dist/src/plugin/debug.js +121 -70
  33. package/dist/src/plugin/debug.js.map +1 -1
  34. package/dist/src/plugin/recovery/constants.d.ts +22 -0
  35. package/dist/src/plugin/recovery/constants.d.ts.map +1 -0
  36. package/dist/src/plugin/recovery/constants.js +43 -0
  37. package/dist/src/plugin/recovery/constants.js.map +1 -0
  38. package/dist/src/plugin/recovery/index.d.ts +12 -0
  39. package/dist/src/plugin/recovery/index.d.ts.map +1 -0
  40. package/dist/src/plugin/recovery/index.js +12 -0
  41. package/dist/src/plugin/recovery/index.js.map +1 -0
  42. package/dist/src/plugin/recovery/storage.d.ts +24 -0
  43. package/dist/src/plugin/recovery/storage.d.ts.map +1 -0
  44. package/dist/src/plugin/recovery/storage.js +354 -0
  45. package/dist/src/plugin/recovery/storage.js.map +1 -0
  46. package/dist/src/plugin/recovery/types.d.ts +116 -0
  47. package/dist/src/plugin/recovery/types.d.ts.map +1 -0
  48. package/dist/src/plugin/recovery/types.js +6 -0
  49. package/dist/src/plugin/recovery/types.js.map +1 -0
  50. package/dist/src/plugin/recovery.d.ts +53 -0
  51. package/dist/src/plugin/recovery.d.ts.map +1 -0
  52. package/dist/src/plugin/recovery.js +347 -0
  53. package/dist/src/plugin/recovery.js.map +1 -0
  54. package/dist/src/plugin/request-helpers.d.ts +10 -3
  55. package/dist/src/plugin/request-helpers.d.ts.map +1 -1
  56. package/dist/src/plugin/request-helpers.js +526 -17
  57. package/dist/src/plugin/request-helpers.js.map +1 -1
  58. package/dist/src/plugin/request.d.ts +1 -0
  59. package/dist/src/plugin/request.d.ts.map +1 -1
  60. package/dist/src/plugin/request.js +40 -69
  61. package/dist/src/plugin/request.js.map +1 -1
  62. package/dist/src/plugin/thinking-recovery.d.ts +64 -0
  63. package/dist/src/plugin/thinking-recovery.d.ts.map +1 -0
  64. package/dist/src/plugin/thinking-recovery.js +245 -0
  65. package/dist/src/plugin/thinking-recovery.js.map +1 -0
  66. package/dist/src/plugin.d.ts.map +1 -1
  67. package/dist/src/plugin.js +89 -10
  68. package/dist/src/plugin.js.map +1 -1
  69. 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
  [![npm version](https://img.shields.io/npm/v/opencode-antigravity-auth.svg)](https://www.npmjs.com/package/opencode-antigravity-auth)
4
+ [![npm beta](https://img.shields.io/npm/v/opencode-antigravity-auth/beta.svg?label=beta)](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 rate limits
11
- - **Dual Gemini quota pools** Gemini models automatically fallback to a second quota pool when the first is exhausted, effectively doubling your Gemini quota per account
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.3"]
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.3"],
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.3"] | unique)' \
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.3\" to the plugin array manually"
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.3"]}' > ~/.config/opencode/opencode.json
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. The fallback is seamless — conversation context is preserved when switching between quota pools.
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
- - **[Claude Model Flow](docs/CLAUDE_MODEL_FLOW.md)** - Full request/response flow, improvements, and fixes
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
@@ -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"}
@@ -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,5 @@
1
+ /**
2
+ * Cache module for opencode-antigravity-auth plugin.
3
+ */
4
+ export { SignatureCache, createSignatureCache, } from "./signature-cache";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -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,5 @@
1
+ /**
2
+ * Cache module for opencode-antigravity-auth plugin.
3
+ */
4
+ export { SignatureCache, createSignatureCache, } from "./signature-cache";
5
+ //# sourceMappingURL=index.js.map
@@ -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