pi-antigravity-rotator 1.13.0 → 2.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,43 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ### Added
6
+ - **Hybrid Routing Policy**: Added optional `routingPolicy: "hybrid"` with weighted selection across timer priority, quota, tier, health, local token bucket state, and distance.
7
+ - **Routing Inspector**: Added a dashboard modal that explains the currently selected route, candidate scores, and why each account was excluded for a model.
8
+ - **Rate Limit Parser Module**: Extracted robust retry parsing into `src/rate-limit-parser.ts` with support for `Retry-After`, `x-ratelimit-reset`, `quotaResetDelay`, `quotaResetTimeStamp`, `retryDelay`, and duration strings.
9
+
10
+ ### Changed
11
+ - **Token Bucket Guardrail**: Added optional per-account token buckets to slow repeated reuse of the same account without changing the default v2.0 routing behavior.
12
+ - **Attention Needed Coverage**: The dashboard now surfaces unroutable models and token-bucket exhaustion alongside existing security, cooldown, disabled, flagged, and error alerts.
13
+ - **Compat Hardening**: Added coverage for `cache_control` stripping, schema forwarding, missing-signature tool history, and empty SSE parsing.
14
+
15
+ ## [2.0.0] - 2026-05-20
16
+
17
+ ### Added
18
+ - **Admin Config APIs**: Added `GET /api/config`, `PUT /api/config`, `GET /api/config/export`, and `POST /api/config/import` for validated runtime config management.
19
+ - **Dashboard Config Editor**: Added an embedded JSON editor with load/save/import/export controls and hosted login access.
20
+ - **Docker Deployment**: Added `Dockerfile`, `docker-compose.yml`, and `.dockerignore` for headless deployments with persistent `/data`.
21
+ - **Doctor Command**: Added `pi-antigravity-rotator doctor` to validate config, inspect backups, and report missing admin auth.
22
+ - **Gemini-Compatible Discovery**: Added `/v1beta/models` and a minimal Gemini-style `generateContent` route family.
23
+
24
+ ### Changed
25
+ - **Version 2.0**: Bumped package version to `2.0.0` on branch `v2.0`.
26
+ - **Persistence Hardening**: Config, state, and token usage now write atomically with timestamped backups.
27
+ - **Routing Metadata**: Added optional account `tier` plus runtime `healthScore` as timer-first tie-breakers.
28
+ - **Security Visibility**: Startup logs, `/api/status`, and the dashboard now warn when `PI_ROTATOR_ADMIN_TOKEN` is missing.
29
+
30
+ ### Migration
31
+ - Existing `accounts.json` stays compatible. New defaults are `bindHost: "0.0.0.0"`, `routingPolicy: "timer-first"`, and `accounts[].tier: "unknown"`.
32
+
33
+ ## [1.14.0] - 2026-05-19
34
+
35
+ ### Added
36
+ - **Gemini 3.5 Flash Support**: Added routing and dashboard support for the new `gemini-3.5-flash` model family (including `gemini-3.5-flash-low` / `gemini-3.5-flash-medium` and `gemini-3-flash-agent` / `gemini-3.5-flash-high`).
37
+ - **GPT-OSS 120B Support**: Added complete support (pricing, styling, and dashboard visualization) for the `gpt-oss-120b-medium` model, mapping its quota tracking to the shared Claude pool (`claude-opus-4-6-thinking`).
38
+ - **Model Role Support**: Added support for the `"model"` role in compatibility layer chat completions, validating and mapping it to native Gemini model turns.
39
+ - **Request Normalization**: Added normalization helpers (`normalizeOpenAIChatCompletionRequest` / `normalizeAnthropicMessagesRequest`) to automatically format loose inputs, Responses-style inputs (e.g., `input`, `prompt`), and raw native Antigravity request payloads into standard OpenAI/Anthropic messages format.
40
+
3
41
  ## [1.13.0] - 2026-05-19
4
42
 
5
43
  ### Removed
package/README.md CHANGED
@@ -1,7 +1,40 @@
1
+ ![Pi Antigravity Rotator logo](./pi-antigravity-rotator_logo.png)
2
+
1
3
  # Pi Antigravity Rotator
2
4
 
3
5
  Multi-account rotation proxy for Google Antigravity. Distributes API usage across multiple Google accounts with per-model routing, real-time quota tracking, automatic token management, and infringement detection.
4
6
 
7
+ > **⚠️ WARNING:** Using this proxy may put connected Google accounts at risk of Terms of Service enforcement, including restriction, suspension, or permanent bans. Use at your own risk.
8
+
9
+ <details>
10
+ <summary><strong>⚠️ Terms of Service Warning — Read Before Installing</strong></summary>
11
+
12
+ > [!CAUTION]
13
+ > This is an unofficial tool and is not endorsed by Google. Routing traffic through this proxy may violate Google's Terms of Service or trigger automated abuse or policy enforcement systems.
14
+ >
15
+ > **By using this proxy, you acknowledge:**
16
+ > - Your account may be restricted, suspended, shadow-banned, or permanently banned
17
+ > - Multi-account rotation and proxying can increase account risk compared to normal interactive usage
18
+ > - You assume all responsibility for the accounts and traffic routed through this tool
19
+ >
20
+ > **Recommendation:** Do not use your primary Google account. Prefer disposable or lower-risk accounts, and keep account exposure conservative.
21
+
22
+ </details>
23
+
24
+ ## Support Me
25
+
26
+ If this tool has helped you optimize your API usage and save costs, consider supporting its development!
27
+
28
+ <a href="https://ko-fi.com/tuxevil" target="_blank"><img src="https://storage.ko-fi.com/cdn/kofi2.png?v=3" height="36" alt="Buy Me a Coffee at ko-fi.com" /></a>
29
+
30
+ ## v2.0 Highlights
31
+
32
+ - Full dashboard config editor with import/export.
33
+ - Official Docker and compose deployment.
34
+ - `pi-antigravity-rotator doctor` for config/state validation.
35
+ - Optional account `tier` metadata and runtime `healthScore`.
36
+ - Strong security warnings when admin routes are open without `PI_ROTATOR_ADMIN_TOKEN`.
37
+
5
38
  ## Features
6
39
 
7
40
  - **Per-model routing** -- Each model (Gemini Pro, Flash, Claude) routes to its own active account independently. Multiple agents using different models won't interfere with each other.
@@ -49,6 +82,14 @@ npm run login
49
82
  npm start
50
83
  ```
51
84
 
85
+ ### Option C: Docker
86
+
87
+ ```bash
88
+ docker compose up -d
89
+ ```
90
+
91
+ The included compose file persists runtime data under `./docker-data` and sets `PI_ROTATOR_DIR=/data`.
92
+
52
93
  ## Adding Accounts
53
94
 
54
95
  Run `npm run login` once per Google account:
@@ -74,7 +115,7 @@ This package is not exclusive to Pi. It can be consumed by **any** agent or fron
74
115
  2. Add a new generic/OpenAI-compatible provider
75
116
  3. Set the API Base URL to: `http://127.0.0.1:51200/v1/`
76
117
  4. Set the API Key to: `antigravity` (or any string, the proxy doesn't validate it)
77
- 5. You can now use any of the models (e.g. `gemini-3-flash`, `gemini-3.1-pro-low`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`) directly in your agent.
118
+ 5. You can now use any of the models (e.g. `gemini-3.5-flash-low`, `gemini-3.5-flash-high`, `gemini-3.1-pro-low`, `claude-sonnet-4-6`, `claude-opus-4-6-thinking`, `gpt-oss-120b-medium`) directly in your agent.
78
119
 
79
120
  ### Activation rule
80
121
 
@@ -89,6 +130,8 @@ If login fails at project discovery:
89
130
 
90
131
  After starting the proxy, open `http://localhost:51200/dashboard` or `http://<your-server-ip>:51200/dashboard` from any machine on the same network (the proxy binds to `0.0.0.0`).
91
132
 
133
+ If `PI_ROTATOR_ADMIN_TOKEN` is unset, dashboard and `/api/*` access remains open for backwards compatibility. v2.0 now surfaces loud warnings about that state in startup logs, `/api/status`, and the dashboard itself.
134
+
92
135
  The dashboard shows:
93
136
 
94
137
  - **Top Status & Controls** -- Real-time routing state, uptime, requests, and PII masking toggle.
@@ -98,7 +141,8 @@ The dashboard shows:
98
141
  - **Quota Forecast** -- Predictive modeling showing when each model's quota will run out based on the current requests/hour burn rate.
99
142
  - **Searchable Request Log** -- Live feed of the last 200 requests with exact timestamps, models, masked accounts, status codes, and latency.
100
143
  - **Account Cards** -- Sorted by total quota. Shows status (`active`, `ready`, `cooldown`, `flagged`, `disabled`), quota bars with timers, and precise error messages.
101
- - **Operator Panels** -- "Attention Needed" summaries for quarantined accounts and a real-time event feed of rotator actions.
144
+ - **Operator Panels** -- "Attention Needed" summaries for quarantined accounts, unroutable models, token-bucket pressure, and a real-time event feed of rotator actions.
145
+ - **Routing Inspector** -- On-demand modal showing the active routing policy, candidate scores, local token bucket state, and rejection reasons per model.
102
146
 
103
147
  ![Dashboard](dashboard.png)
104
148
 
@@ -220,6 +264,8 @@ export PI_ROTATOR_DIR=/path/to/config
220
264
  export PI_ROTATOR_QUOTA_USER_AGENT="antigravity/1.107.0 darwin/arm64"
221
265
  # Optional: require this token for dashboard/API access. If unset, legacy open access is preserved.
222
266
  export PI_ROTATOR_ADMIN_TOKEN="change-me"
267
+ # Optional: bind the proxy to a safer local-only interface.
268
+ export PI_ROTATOR_BIND_HOST="127.0.0.1"
223
269
  # Optional: max accepted proxy request body size in bytes. Default: 26214400 (25 MiB).
224
270
  export PI_ROTATOR_MAX_BODY_BYTES=26214400
225
271
  # Optional: log verbosity. One of debug, info, warn, error, silent. Default: info.
@@ -231,6 +277,24 @@ export PI_AI_ANTIGRAVITY_VERSION=1.107.0
231
277
  pi-antigravity-rotator start --config-dir /path/to/config
232
278
  ```
233
279
 
280
+ New v2.0 config fields:
281
+
282
+ - `bindHost`: interface to bind on. Default: `0.0.0.0`.
283
+ - `routingPolicy`: current default is `timer-first`. Optional values now include `tier-first`, `quota-first`, and `hybrid`.
284
+ - `tokenBucketEnabled`: enables the local per-account request bucket used by `hybrid`. Default: `false`.
285
+ - `tokenBucketMaxTokens`: bucket capacity when enabled. Default: `50`.
286
+ - `tokenBucketRefillPerMinute`: refill speed when enabled. Default: `6`.
287
+ - `tokenBucketInitialTokens`: startup fill level when enabled. Default: `50`.
288
+ - `accounts[].tier`: optional `ultra`, `pro`, `free`, or `unknown`.
289
+
290
+ ## Doctor
291
+
292
+ ```bash
293
+ pi-antigravity-rotator doctor
294
+ ```
295
+
296
+ This validates `accounts.json`, checks local state files, lists backups, and warns when admin auth is not configured.
297
+
234
298
  `accounts.json` is created automatically by the login command.
235
299
  Login now fails if Google does not return a project ID. No shared fallback.
236
300
 
@@ -353,7 +417,9 @@ curl http://localhost:51200/v1/messages \
353
417
  Current adapter scope:
354
418
 
355
419
  - Text chat/messages.
356
- - **Native Reasoning visibility**: Models with thinking capabilities (Gemini 3 Pro, Gemini 3 Flash, Claude Sonnet 4.6 Thinking) automatically expose their interleaved thinking blocks in real-time as OpenAI `reasoning_content` or Anthropic `thinking_delta` chunks.
420
+ - **Model Role Support**: Fully supports the `"model"` role in chat message histories (e.g., from Pi or Hermes agents), validating and routing it identically to the `"assistant"` role.
421
+ - **Request Normalization**: Automatically normalizes loose inputs (non-array messages), legacy prompt/input fields (e.g. `prompt` strings/arrays or `input` structures), and raw native Antigravity requests (`request.contents`) into standard OpenAI/Anthropic format.
422
+ - **Native Reasoning visibility**: Models with thinking capabilities (Gemini 3 Pro, Gemini 3.5 Flash, Claude Sonnet 4.6 Thinking) automatically expose their interleaved thinking blocks in real-time as OpenAI `reasoning_content` or Anthropic `thinking_delta` chunks.
357
423
  - Streaming mode is supported as compatibility SSE. The adapter buffers the upstream Antigravity stream, then emits one OpenAI/Anthropic-compatible final delta. Native token-by-token pass-through is not implemented yet.
358
424
  - Image input is supported when sent as base64 data URL (`OpenAI image_url.url = data:image/...;base64,...`) or Anthropic base64 source (`type=image`, `source.type=base64`).
359
425
  - **Tool/function calling is fully supported** (OpenAI `tools`/`tool_choice` format and Anthropic `tool_use`/`tool_result` via standard translation to Gemini `functionDeclarations`).
@@ -461,9 +527,3 @@ export PI_ROTATOR_TELEMETRY=off
461
527
  ```
462
528
 
463
529
  Or use any of: `PI_ROTATOR_TELEMETRY=false`, `PI_ROTATOR_TELEMETRY=0`.
464
-
465
- ## Support Me
466
-
467
- If this tool has helped you optimize your API usage and save costs, consider supporting its development!
468
-
469
- <a href="https://ko-fi.com/tuxevil" target="_blank"><img src="https://storage.ko-fi.com/cdn/kofi2.png?v=3" height="36" alt="Buy Me a Coffee at ko-fi.com" /></a>
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-antigravity-rotator",
3
- "version": "1.13.0",
3
+ "version": "2.0.0",
4
4
  "description": "Multi-account rotation proxy for Google Antigravity with per-model routing, real-time quota tracking, and infringement detection",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -1,24 +1,59 @@
1
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
1
+ import { existsSync, mkdirSync, readFileSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
4
  import { getAccountsPath } from "./paths.js";
5
5
  import type { AccountConfig, Config } from "./types.js";
6
+ import { backupFile, readJsonFile, writeJsonFileAtomic } from "./storage.js";
7
+ import { formatValidationErrors, validateConfig } from "./validators.js";
6
8
 
7
9
  const ACCOUNTS_FILE = getAccountsPath();
8
10
  const PI_DIR = join(homedir(), ".pi", "agent");
9
11
  const PI_MODELS_FILE = join(PI_DIR, "models.json");
10
12
  const PI_AUTH_FILE = join(PI_DIR, "auth.json");
13
+ const TOKEN_USAGE_FILE = join(join(ACCOUNTS_FILE, ".."), "token-usage.json");
11
14
 
12
- export function loadOrCreateAccountsConfig(): Config {
13
- if (existsSync(ACCOUNTS_FILE)) {
14
- try {
15
- return JSON.parse(readFileSync(ACCOUNTS_FILE, "utf-8")) as Config;
16
- } catch {
17
- // Corrupted, start fresh
18
- }
19
- }
15
+ export function getTokenUsagePath(): string {
16
+ return TOKEN_USAGE_FILE;
17
+ }
18
+
19
+ export function applyConfigDefaults(config: Config): Config {
20
20
  return {
21
+ proxyPort: config.proxyPort || 51200,
22
+ bindHost: config.bindHost || process.env.PI_ROTATOR_BIND_HOST || "0.0.0.0",
23
+ routingPolicy: config.routingPolicy || "timer-first",
24
+ requestsPerRotation: config.requestsPerRotation || 5,
25
+ rotateOnQuotaDrop: config.rotateOnQuotaDrop ?? 20,
26
+ quotaPollIntervalMs: config.quotaPollIntervalMs || 300000,
27
+ maxConcurrentRequestsPerAccount: config.maxConcurrentRequestsPerAccount ?? 1,
28
+ maxConcurrentRequestsPerProjectModel: config.maxConcurrentRequestsPerProjectModel ?? 1,
29
+ projectCircuitBreaker429Threshold: config.projectCircuitBreaker429Threshold ?? 3,
30
+ projectCircuitBreakerWindowMs: config.projectCircuitBreakerWindowMs ?? 10 * 60 * 1000,
31
+ projectCircuitBreakerCooldownMs: config.projectCircuitBreakerCooldownMs ?? 60 * 60 * 1000,
32
+ modelCircuitBreaker429Threshold: config.modelCircuitBreaker429Threshold ?? 3,
33
+ modelCircuitBreakerCooldownMs: config.modelCircuitBreakerCooldownMs ?? 6 * 60 * 60 * 1000,
34
+ dailyAccountSlowRequests: config.dailyAccountSlowRequests ?? 250,
35
+ dailyAccountStopRequests: config.dailyAccountStopRequests ?? 350,
36
+ dailyProjectSlowRequests: config.dailyProjectSlowRequests ?? 900,
37
+ dailyProjectStopRequests: config.dailyProjectStopRequests ?? 1200,
38
+ slowModeJitterMinMs: config.slowModeJitterMinMs ?? 8_000,
39
+ slowModeJitterMaxMs: config.slowModeJitterMaxMs ?? 25_000,
40
+ protectivePauseMs: config.protectivePauseMs ?? 21600000,
41
+ useRequestCountRotationWhenQuotaUnknownOnly: config.useRequestCountRotationWhenQuotaUnknownOnly ?? true,
42
+ tokenBucketEnabled: config.tokenBucketEnabled ?? false,
43
+ tokenBucketMaxTokens: config.tokenBucketMaxTokens ?? 50,
44
+ tokenBucketRefillPerMinute: config.tokenBucketRefillPerMinute ?? 6,
45
+ tokenBucketInitialTokens: config.tokenBucketInitialTokens ?? (config.tokenBucketMaxTokens ?? 50),
46
+ accounts: config.accounts.map((account) => ({
47
+ ...account,
48
+ tier: account.tier || "unknown",
49
+ })),
50
+ };
51
+ }
52
+
53
+ export function getDefaultConfig(): Config {
54
+ return applyConfigDefaults({
21
55
  proxyPort: 51200,
56
+ accounts: [],
22
57
  requestsPerRotation: 5,
23
58
  rotateOnQuotaDrop: 20,
24
59
  quotaPollIntervalMs: 300000,
@@ -37,12 +72,34 @@ export function loadOrCreateAccountsConfig(): Config {
37
72
  slowModeJitterMaxMs: 25_000,
38
73
  protectivePauseMs: 21600000,
39
74
  useRequestCountRotationWhenQuotaUnknownOnly: true,
40
- accounts: [],
41
- };
75
+ tokenBucketEnabled: false,
76
+ tokenBucketMaxTokens: 50,
77
+ tokenBucketRefillPerMinute: 6,
78
+ tokenBucketInitialTokens: 50,
79
+ });
80
+ }
81
+
82
+ export function loadConfigFromDisk(): Config {
83
+ const parsed = readJsonFile<unknown>(ACCOUNTS_FILE);
84
+ if (parsed === null) return getDefaultConfig();
85
+ const validation = validateConfig(parsed);
86
+ if (!validation.ok || !validation.value) {
87
+ throw new Error(formatValidationErrors(validation.errors));
88
+ }
89
+ return applyConfigDefaults(validation.value);
90
+ }
91
+
92
+ export function loadOrCreateAccountsConfig(): Config {
93
+ try {
94
+ return loadConfigFromDisk();
95
+ } catch {
96
+ return getDefaultConfig();
97
+ }
42
98
  }
43
99
 
44
100
  export function saveAccountsConfig(config: Config): void {
45
- writeFileSync(ACCOUNTS_FILE, JSON.stringify(config, null, 2) + "\n", "utf-8");
101
+ backupFile(ACCOUNTS_FILE, "accounts");
102
+ writeJsonFileAtomic(ACCOUNTS_FILE, applyConfigDefaults(config));
46
103
  }
47
104
 
48
105
  export function addAccountToConfig(entry: AccountConfig): { isNew: boolean } {
@@ -83,7 +140,7 @@ export function ensurePiModelsConfig(): void {
83
140
  providers["google-antigravity"] = antigravity;
84
141
  models.providers = providers;
85
142
 
86
- writeFileSync(PI_MODELS_FILE, JSON.stringify(models, null, 2) + "\n", "utf-8");
143
+ writeJsonFileAtomic(PI_MODELS_FILE, models);
87
144
  console.log(` Updated ${PI_MODELS_FILE}`);
88
145
  }
89
146
 
@@ -112,6 +169,6 @@ export function ensurePiAuthConfig(): void {
112
169
  projectId: "proxy-managed",
113
170
  };
114
171
 
115
- writeFileSync(PI_AUTH_FILE, JSON.stringify(auth, null, 2) + "\n", "utf-8");
172
+ writeJsonFileAtomic(PI_AUTH_FILE, auth);
116
173
  console.log(` Updated ${PI_AUTH_FILE}`);
117
174
  }
package/src/cli.ts CHANGED
@@ -38,6 +38,13 @@ switch (command) {
38
38
  }
39
39
  break;
40
40
  }
41
+ case "doctor": {
42
+ const { printDoctorReport, runDoctor } = await import("./doctor.js");
43
+ const result = runDoctor();
44
+ printDoctorReport(result);
45
+ process.exit(result.ok ? 0 : 1);
46
+ break;
47
+ }
41
48
  default:
42
49
  console.log("Pi Antigravity Rotator");
43
50
  console.log();
@@ -45,6 +52,7 @@ switch (command) {
45
52
  console.log(" pi-antigravity-rotator start Start the proxy (default)");
46
53
  console.log(" pi-antigravity-rotator login Add a new Google account");
47
54
  console.log(" pi-antigravity-rotator status Show account status (JSON)");
55
+ console.log(" pi-antigravity-rotator doctor Validate config and local state");
48
56
  console.log();
49
57
  console.log("Options:");
50
58
  console.log(" --config-dir <path> Config directory (default: ~/.pi-antigravity-rotator/)");