pi-antigravity-rotator 2.1.3 → 2.1.4

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
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [2.1.4] - 2026-05-27
6
+
7
+ ### Improved
8
+ - **Less Lossy Schema Collapsing for Claude**: The `sanitizeClaudeViaGeminiSchema` function now handles complex `anyOf`/`oneOf`/`allOf` schemas with significantly less information loss:
9
+ - **Nullable detection (lossless)**: `anyOf: [{type: X}, {type: "null"}]` patterns are now converted to `{type: X, nullable: true}` instead of losing the null variant.
10
+ - **`allOf` deep merge (lossless)**: `allOf` variants are now deep-merged (properties union + required union) instead of picking only the first variant.
11
+ - **`anyOf`/`oneOf` object merge**: When all variants are objects, properties are merged into a union and only fields required in ALL variants remain required, preserving wider input acceptance.
12
+ - The first-variant fallback is still used for truly incompatible mixed-type unions.
13
+
14
+ ### Fixed
15
+ - **README: Incorrect model names in Codex section**: Removed references to nonexistent `claude-3-5-sonnet` and `gemini-3-pro` models, replaced with actual supported models (`claude-opus-4-6-thinking`, `gemini-3.1-pro`, `gpt-oss-120b`, etc.).
16
+
17
+ ### Added
18
+ - **Schema sanitizer tests**: Added test cases for nullable detection, `allOf` deep merge, and `anyOf` object variant merging.
19
+
5
20
  ## [2.1.3] - 2026-05-27
6
21
 
7
22
  ### Fixed
package/README.md CHANGED
@@ -500,9 +500,10 @@ To connect Codex to your local rotator:
500
500
 
501
501
  3. **Select a Supported Model**:
502
502
  Configure Codex to target one of the following models supported by the rotator (which will be mapped to the best available Google Antigravity account/model under the hood):
503
- - `gemini-3.5-flash` or `gemini-3.5-flash-high` / `gemini-3.5-flash-low` (Recommended for fast general reasoning)
504
- - `gemini-3-pro` or `gemini-pro-agent` (For deep reasoning)
505
- - `claude-sonnet-4-6` or `claude-3-5-sonnet` (Alternative routing fallback)
503
+ - `gemini-3.5-flash` or `gemini-3.5-flash-high` / `gemini-3.5-flash-low` / `gemini-3.5-flash-medium` (Recommended for fast general reasoning)
504
+ - `gemini-3.1-pro` or `gemini-pro-agent` / `gemini-3.1-pro-high` / `gemini-3.1-pro-low` (For deep reasoning)
505
+ - `claude-sonnet-4-6` or `claude-opus-4-6-thinking` (Claude models via Vertex AI)
506
+ - `gpt-oss-120b` or `gpt-oss-120b-medium` (Open-source GPT model)
506
507
 
507
508
  Example Codex configuration entry:
508
509
  ```json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pi-antigravity-rotator",
3
- "version": "2.1.3",
3
+ "version": "2.1.4",
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",
package/src/compat.ts CHANGED
@@ -470,17 +470,98 @@ function sanitizeClaudeViaGeminiSchema(schema: unknown): unknown {
470
470
  }
471
471
  }
472
472
 
473
- // General case: collapse to the first valid variant.
474
- // Gemini's Schema proto serialization corrupts complex anyOf/oneOf
475
- // during the round-trip to Claude, causing JSON Schema draft 2020-12
476
- // validation failures. Collapsing is lossy but functional — the tool
477
- // still works, just with a narrower accepted input type.
473
+ // Sanitize all variants first.
478
474
  const cleaned = value.map(sanitizeClaudeViaGeminiSchema).filter(
479
475
  (v) => isRecord(v) && Object.keys(v).length > 0,
476
+ ) as Record<string, unknown>[];
477
+
478
+ if (cleaned.length === 0) {
479
+ // All variants collapsed to nothing — skip entirely.
480
+ continue;
481
+ }
482
+
483
+ // Case 3: nullable pattern — anyOf/oneOf with exactly one {type:"null"}
484
+ // variant and one or more real variants. Convert to the real variant
485
+ // with nullable:true. This is lossless — Gemini's proto supports nullable.
486
+ // e.g. anyOf:[{type:"string"},{type:"null"}] → {type:"string",nullable:true}
487
+ if (key !== "allOf") {
488
+ const nullIdx = cleaned.findIndex((v) => v.type === "null" && Object.keys(v).length === 1);
489
+ if (nullIdx !== -1) {
490
+ const nonNull = cleaned.filter((_, i) => i !== nullIdx);
491
+ if (nonNull.length === 1) {
492
+ Object.assign(out, nonNull[0], { nullable: true });
493
+ continue;
494
+ }
495
+ if (nonNull.length > 1) {
496
+ // Multiple non-null variants + null → collapse non-null variants,
497
+ // then mark nullable. Still lossy but preserves nullability.
498
+ Object.assign(out, nonNull[0], { nullable: true });
499
+ continue;
500
+ }
501
+ }
502
+ }
503
+
504
+ // Case 4: allOf — deep merge all variants (allOf = intersection).
505
+ // Merging properties from all variants is semantically correct.
506
+ if (key === "allOf") {
507
+ const merged: Record<string, unknown> = {};
508
+ let mergedProperties: Record<string, unknown> = {};
509
+ let mergedRequired: string[] = [];
510
+ for (const variant of cleaned) {
511
+ for (const [vk, vv] of Object.entries(variant)) {
512
+ if (vk === "properties" && isRecord(vv)) {
513
+ mergedProperties = { ...mergedProperties, ...vv };
514
+ } else if (vk === "required" && Array.isArray(vv)) {
515
+ mergedRequired = [...new Set([...mergedRequired, ...vv])];
516
+ } else {
517
+ merged[vk] = vv;
518
+ }
519
+ }
520
+ }
521
+ if (Object.keys(mergedProperties).length > 0) merged["properties"] = mergedProperties;
522
+ if (mergedRequired.length > 0) merged["required"] = mergedRequired;
523
+ Object.assign(out, merged);
524
+ continue;
525
+ }
526
+
527
+ // Case 5: anyOf/oneOf where all variants are objects with properties —
528
+ // merge all properties together, making all optional (union of shapes).
529
+ // This is mildly lossy (accepts wider input) but doesn't reject valid inputs.
530
+ const allObjects = cleaned.every(
531
+ (v) => v.type === "object" && isRecord(v.properties),
480
532
  );
481
- if (cleaned.length >= 1) {
482
- Object.assign(out, cleaned[0]);
533
+ if (allObjects && cleaned.length > 1) {
534
+ const unionProperties: Record<string, unknown> = {};
535
+ for (const variant of cleaned) {
536
+ const props = variant.properties as Record<string, unknown>;
537
+ for (const [pk, pv] of Object.entries(props)) {
538
+ if (!(pk in unionProperties)) unionProperties[pk] = pv;
539
+ }
540
+ }
541
+ // Only keep required fields that exist in ALL variants
542
+ const allRequired = cleaned.map((v) =>
543
+ Array.isArray(v.required) ? new Set(v.required as string[]) : new Set<string>(),
544
+ );
545
+ const commonRequired = [...allRequired[0]].filter((r) =>
546
+ allRequired.every((s) => s.has(r)),
547
+ );
548
+ const base = { ...cleaned[0] };
549
+ base["properties"] = unionProperties;
550
+ if (commonRequired.length > 0) {
551
+ base["required"] = commonRequired;
552
+ } else {
553
+ delete base["required"];
554
+ }
555
+ Object.assign(out, base);
556
+ continue;
483
557
  }
558
+
559
+ // Fallback: collapse to the first valid variant.
560
+ // Gemini's Schema proto serialization corrupts complex anyOf/oneOf
561
+ // during the round-trip to Claude, causing JSON Schema draft 2020-12
562
+ // validation failures. Collapsing is lossy but functional — the tool
563
+ // still works, just with a narrower accepted input type.
564
+ Object.assign(out, cleaned[0]);
484
565
  }
485
566
  continue;
486
567
  }