clawmoney 0.13.9 → 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
|
-
|
|
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
|
-
|
|
94
|
-
"
|
|
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",
|
|
@@ -536,28 +543,33 @@ async function doCallAntigravityApi(opts) {
|
|
|
536
543
|
if (upstreamModel !== opts.model) {
|
|
537
544
|
logger.info(`[antigravity-api] model mapping: ${opts.model} → ${upstreamModel}`);
|
|
538
545
|
}
|
|
546
|
+
// Request shape mirrors sub2api's minimal inner GeminiRequest: contents +
|
|
547
|
+
// toolConfig + sessionId. generationConfig is optional and only gets set
|
|
548
|
+
// when the caller actually supplied max_tokens — sending a bare
|
|
549
|
+
// maxOutputTokens in the inner request against v1internal has been
|
|
550
|
+
// observed to return "500 Unknown Error" for some models.
|
|
551
|
+
const innerRequest = {
|
|
552
|
+
contents: [
|
|
553
|
+
{
|
|
554
|
+
role: "user",
|
|
555
|
+
parts: [{ text: prompt }],
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
toolConfig: {
|
|
559
|
+
functionCallingConfig: { mode: "VALIDATED" },
|
|
560
|
+
},
|
|
561
|
+
sessionId: generateStableSessionId(prompt),
|
|
562
|
+
};
|
|
539
563
|
const outerRequest = {
|
|
540
564
|
project: projectId,
|
|
541
565
|
requestId: `agent-${randomUUID()}`,
|
|
542
566
|
userAgent: "antigravity",
|
|
543
567
|
requestType: "agent",
|
|
544
568
|
model: upstreamModel,
|
|
545
|
-
request:
|
|
546
|
-
contents: [
|
|
547
|
-
{
|
|
548
|
-
role: "user",
|
|
549
|
-
parts: [{ text: prompt }],
|
|
550
|
-
},
|
|
551
|
-
],
|
|
552
|
-
generationConfig: {
|
|
553
|
-
maxOutputTokens: maxTokens,
|
|
554
|
-
},
|
|
555
|
-
toolConfig: {
|
|
556
|
-
functionCallingConfig: { mode: "VALIDATED" },
|
|
557
|
-
},
|
|
558
|
-
sessionId: generateStableSessionId(prompt),
|
|
559
|
-
},
|
|
569
|
+
request: innerRequest,
|
|
560
570
|
};
|
|
571
|
+
// Suppress unused-var lint while we keep maxTokens around for future use.
|
|
572
|
+
void maxTokens;
|
|
561
573
|
const bodyJson = JSON.stringify(outerRequest);
|
|
562
574
|
let transientAttempt = 0;
|
|
563
575
|
let hasRefreshed = false;
|
|
@@ -584,8 +596,7 @@ async function doCallAntigravityApi(opts) {
|
|
|
584
596
|
throw err;
|
|
585
597
|
}
|
|
586
598
|
if (resp.ok) {
|
|
587
|
-
const
|
|
588
|
-
const parsed = parseAntigravityResponse(data, opts.model);
|
|
599
|
+
const parsed = await parseAntigravitySseResponse(resp, opts.model);
|
|
589
600
|
recordAntigravitySpend(parsed, opts.model);
|
|
590
601
|
return parsed;
|
|
591
602
|
}
|
|
@@ -635,23 +646,89 @@ function recordAntigravitySpend(parsed, model) {
|
|
|
635
646
|
const cost = calculateCost(model, input_tokens, output_tokens, cache_creation_tokens, cache_read_tokens);
|
|
636
647
|
rateGuard.recordSpend(cost.apiCost);
|
|
637
648
|
}
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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
|
+
}
|
|
647
724
|
return {
|
|
648
725
|
text,
|
|
649
726
|
sessionId: "",
|
|
650
727
|
usage: {
|
|
651
|
-
input_tokens: Math.max(0,
|
|
652
|
-
output_tokens:
|
|
728
|
+
input_tokens: Math.max(0, inputTokens - cachedTokens),
|
|
729
|
+
output_tokens: outputTokens,
|
|
653
730
|
cache_creation_tokens: 0,
|
|
654
|
-
cache_read_tokens:
|
|
731
|
+
cache_read_tokens: cachedTokens,
|
|
655
732
|
},
|
|
656
733
|
model: fallbackModel,
|
|
657
734
|
costUsd: 0,
|