clawmoney 0.13.10 → 0.13.12

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.
@@ -79,7 +79,11 @@ const ANTIGRAVITY_LOAD_ENDPOINTS = [
79
79
  ANTIGRAVITY_ENDPOINT_PROD,
80
80
  ANTIGRAVITY_ENDPOINT_DAILY,
81
81
  ];
82
- const GENERATE_PATH = "/v1internal:generateContent";
82
+ // Antigravity upstream only supports streamGenerateContent — the non-stream
83
+ // variant returns a generic 500 "Unknown Error" for every model we tested.
84
+ // Documented in sub2api/backend/internal/service/antigravity_gateway_service.go:1409
85
+ // ("Antigravity 上游只支持流式请求"). We parse the ?alt=sse response inline.
86
+ const GENERATE_PATH = "/v1internal:streamGenerateContent?alt=sse";
83
87
  /**
84
88
  * Map our `antigravity-*` market-facing model IDs to the real model names
85
89
  * Google's v1internal endpoint accepts. The `antigravity-` prefix only
@@ -90,8 +94,11 @@ const GENERATE_PATH = "/v1internal:generateContent";
90
94
  * names.
91
95
  */
92
96
  const ANTIGRAVITY_MODEL_MAP = {
93
- "antigravity-gemini-3-pro": "gemini-3-pro-high",
94
- "antigravity-gemini-3.1-pro": "gemini-3-pro-high",
97
+ // Gemini 3 Pro was retired in April 2026 — Google now returns a plain-text
98
+ // "no longer available, switch to Gemini 3.1 Pro" body if you ask for it.
99
+ // Route both the 3-pro and 3.1-pro market IDs to 3.1-pro-high.
100
+ "antigravity-gemini-3-pro": "gemini-3.1-pro-high",
101
+ "antigravity-gemini-3.1-pro": "gemini-3.1-pro-high",
95
102
  "antigravity-gemini-3-flash": "gemini-3-flash",
96
103
  "antigravity-gemini-2.5-pro": "gemini-2.5-pro",
97
104
  "antigravity-gemini-2.5-flash": "gemini-2.5-flash",
@@ -589,8 +596,7 @@ async function doCallAntigravityApi(opts) {
589
596
  throw err;
590
597
  }
591
598
  if (resp.ok) {
592
- const data = (await resp.json());
593
- const parsed = parseAntigravityResponse(data, opts.model);
599
+ const parsed = await parseAntigravitySseResponse(resp, opts.model);
594
600
  recordAntigravitySpend(parsed, opts.model);
595
601
  return parsed;
596
602
  }
@@ -640,23 +646,89 @@ function recordAntigravitySpend(parsed, model) {
640
646
  const cost = calculateCost(model, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens);
641
647
  rateGuard.recordSpend(cost.apiCost);
642
648
  }
643
- function parseAntigravityResponse(data, fallbackModel) {
644
- const response = data.response ?? {};
645
- const candidates = response.candidates ?? [];
646
- const firstCandidate = candidates[0];
647
- const text = (firstCandidate?.content?.parts ?? [])
648
- .map((p) => p.text ?? "")
649
- .join("");
650
- const usage = response.usageMetadata ?? {};
651
- const cached = usage.cachedContentTokenCount ?? 0;
649
+ /**
650
+ * Parse Antigravity's streamGenerateContent?alt=sse response. Google sends
651
+ * Server-Sent Events where each line is `data: {json}`; the JSON shape
652
+ * matches a single `V1InternalGenerateResponse` chunk. Text parts accumulate
653
+ * across chunks, and usageMetadata is usually on the last chunk.
654
+ *
655
+ * Note: unlike vanilla Gemini API, Antigravity wraps each chunk's body in a
656
+ * top-level `response` field (mirroring the non-stream shape), so we unwrap.
657
+ */
658
+ async function parseAntigravitySseResponse(resp, fallbackModel) {
659
+ const reader = resp.body?.getReader();
660
+ if (!reader) {
661
+ throw new Error("Antigravity streamGenerateContent returned no body");
662
+ }
663
+ const decoder = new TextDecoder("utf-8");
664
+ let buffer = "";
665
+ let text = "";
666
+ let inputTokens = 0;
667
+ let outputTokens = 0;
668
+ let cachedTokens = 0;
669
+ const processChunk = (jsonStr) => {
670
+ const trimmed = jsonStr.trim();
671
+ if (!trimmed || trimmed === "[DONE]")
672
+ return;
673
+ let chunk;
674
+ try {
675
+ chunk = JSON.parse(trimmed);
676
+ }
677
+ catch {
678
+ return;
679
+ }
680
+ // Antigravity SSE sometimes emits chunks with fields at the top level
681
+ // (without the `response` wrapper). Handle both shapes.
682
+ const body = chunk.response ??
683
+ chunk;
684
+ const candidates = body?.candidates ?? [];
685
+ for (const cand of candidates) {
686
+ for (const part of cand.content?.parts ?? []) {
687
+ if (part.text)
688
+ text += part.text;
689
+ }
690
+ }
691
+ const usage = body?.usageMetadata;
692
+ if (usage) {
693
+ if (typeof usage.promptTokenCount === "number") {
694
+ inputTokens = usage.promptTokenCount;
695
+ }
696
+ if (typeof usage.candidatesTokenCount === "number") {
697
+ outputTokens = usage.candidatesTokenCount;
698
+ }
699
+ if (typeof usage.cachedContentTokenCount === "number") {
700
+ cachedTokens = usage.cachedContentTokenCount;
701
+ }
702
+ }
703
+ };
704
+ while (true) {
705
+ const { value, done } = await reader.read();
706
+ if (done)
707
+ break;
708
+ buffer += decoder.decode(value, { stream: true });
709
+ let newlineIdx;
710
+ while ((newlineIdx = buffer.indexOf("\n")) >= 0) {
711
+ const line = buffer.slice(0, newlineIdx).replace(/\r$/, "");
712
+ buffer = buffer.slice(newlineIdx + 1);
713
+ if (!line)
714
+ continue;
715
+ if (line.startsWith("data:")) {
716
+ processChunk(line.slice(5));
717
+ }
718
+ }
719
+ }
720
+ // Flush tail (unlikely but safe)
721
+ if (buffer.startsWith("data:")) {
722
+ processChunk(buffer.slice(5));
723
+ }
652
724
  return {
653
725
  text,
654
726
  sessionId: "",
655
727
  usage: {
656
- input_tokens: Math.max(0, (usage.promptTokenCount ?? 0) - cached),
657
- output_tokens: usage.candidatesTokenCount ?? 0,
728
+ input_tokens: Math.max(0, inputTokens - cachedTokens),
729
+ output_tokens: outputTokens,
658
730
  cache_creation_tokens: 0,
659
- cache_read_tokens: cached,
731
+ cache_read_tokens: cachedTokens,
660
732
  },
661
733
  model: fallbackModel,
662
734
  costUsd: 0,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clawmoney",
3
- "version": "0.13.10",
3
+ "version": "0.13.12",
4
4
  "description": "ClawMoney CLI -- Earn rewards with your AI agent",
5
5
  "type": "module",
6
6
  "bin": {