pi-provider-antigravity 0.14.0 → 0.15.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/README.md CHANGED
@@ -39,7 +39,9 @@ pi -e npm:pi-provider-antigravity
39
39
 
40
40
  ### Legacy Pi Support
41
41
 
42
- For Pi 0.80.0 ~ 0.85.x, install the compatible `0.13.0` release:
42
+ We strongly recommend upgrading to Pi >= 0.86.0 to take advantage of the latest SDK capabilities, streaming reliability improvements, and full feature parity.
43
+
44
+ However, if you are unable to upgrade your Pi installation just yet (for environment or workflow reasons), you can pin to the compatible `0.13.0` release for Pi 0.80.0 ~ 0.85.x:
43
45
 
44
46
  ```bash
45
47
  pi install npm:pi-provider-antigravity@0.13.0
package/dist/index.js CHANGED
@@ -28,13 +28,15 @@ function resolveToken(auth) {
28
28
  async function postAntigravityRaw({
29
29
  auth,
30
30
  path: path2,
31
+ endpoint = DEFAULT_ENDPOINT,
32
+ headers,
31
33
  body,
32
34
  signal
33
35
  }) {
34
36
  const token = resolveToken(auth);
35
- return fetch(`${DEFAULT_ENDPOINT}/${path2}`, {
37
+ return fetch(`${endpoint}/${path2}`, {
36
38
  method: "POST",
37
- headers: buildAntigravityHeaders(token),
39
+ headers: { ...buildAntigravityHeaders(token), ...headers },
38
40
  body: JSON.stringify(body),
39
41
  signal
40
42
  });
@@ -56,7 +58,7 @@ async function postAntigravityStream(params) {
56
58
  if (!res.body) {
57
59
  throw new Error(`No response stream received from Antigravity for ${params.path}.`);
58
60
  }
59
- return res.body;
61
+ return { response: res, stream: res.body };
60
62
  }
61
63
  function buildAntigravityHeaders(token, userAgent = DEFAULT_USER_AGENT) {
62
64
  return {
@@ -122,7 +124,8 @@ async function fetchProjectId(token, signal) {
122
124
  if (data.cloudaicompanionProject) {
123
125
  return data.cloudaicompanionProject;
124
126
  }
125
- } catch {
127
+ } catch (err) {
128
+ if (signal?.aborted) throw err;
126
129
  }
127
130
  return FALLBACK_PROJECT_ID;
128
131
  }
@@ -142,16 +145,37 @@ async function loginAntigravity(callbacks) {
142
145
  });
143
146
  const authUrl = `${AUTH_URL}?${authParams.toString()}`;
144
147
  callbacks.onAuth({ url: authUrl });
148
+ let timer;
149
+ const abortPromise = new Promise((_, rej) => {
150
+ if (callbacks.signal?.aborted) {
151
+ rej(callbacks.signal.reason ?? new Error("Login cancelled"));
152
+ return;
153
+ }
154
+ callbacks.signal?.addEventListener(
155
+ "abort",
156
+ () => rej(callbacks.signal?.reason ?? new Error("Login cancelled")),
157
+ { once: true }
158
+ );
159
+ });
145
160
  const manualPromptPromise = (async () => {
146
161
  const input = await callbacks.onPrompt({
147
162
  message: "Paste authorization code from browser:"
148
163
  });
149
164
  return extractCodeFromInput(input);
150
165
  })();
151
- const timeout = new Promise(
152
- (_, rej) => setTimeout(() => rej(new Error("Timed out waiting for authorization code (5 minutes)")), OAUTH_CALLBACK_TIMEOUT_MS)
153
- );
154
- const code = await Promise.race([manualPromptPromise, timeout]);
166
+ const timeoutPromise = new Promise((_, rej) => {
167
+ timer = setTimeout(
168
+ () => rej(new Error("Timed out waiting for authorization code (5 minutes)")),
169
+ OAUTH_CALLBACK_TIMEOUT_MS
170
+ );
171
+ });
172
+ let code;
173
+ try {
174
+ code = await Promise.race([manualPromptPromise, timeoutPromise, abortPromise]);
175
+ } finally {
176
+ if (timer) clearTimeout(timer);
177
+ }
178
+ callbacks.onProgress?.("Exchanging authorization code for tokens...");
155
179
  const tokenParams = new URLSearchParams({
156
180
  client_id: CLIENT_ID,
157
181
  client_secret: CLIENT_SECRET,
@@ -163,14 +187,16 @@ async function loginAntigravity(callbacks) {
163
187
  const tokenRes = await fetch(TOKEN_URL, {
164
188
  method: "POST",
165
189
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
166
- body: tokenParams.toString()
190
+ body: tokenParams.toString(),
191
+ signal: callbacks.signal
167
192
  });
168
193
  if (!tokenRes.ok) {
169
194
  const errText = await tokenRes.text();
170
195
  throw new Error(`Failed to exchange token (${tokenRes.status}): ${errText}`);
171
196
  }
172
197
  const tokens = await tokenRes.json();
173
- const projectId = await fetchProjectId(tokens.access_token);
198
+ callbacks.onProgress?.("Resolving Code Assist project ID...");
199
+ const projectId = await fetchProjectId(tokens.access_token, callbacks.signal);
174
200
  return {
175
201
  refresh: tokens.refresh_token || "",
176
202
  access: JSON.stringify({ token: tokens.access_token, projectId }),
@@ -527,6 +553,7 @@ function synthesizeDynamicModel(baseId, items) {
527
553
  const isGpt = family === "gpt";
528
554
  const defaultContext = isFlash ? 1048576 : isClaude ? 25e4 : isGpt ? 128e3 : 1048576;
529
555
  const defaultMaxOutput = isClaude ? 64e3 : isGpt ? 32768 : 65536;
556
+ const promptCache = isClaude ? { short: 300, long: 3600 } : { short: 300 };
530
557
  return {
531
558
  id: baseId,
532
559
  name: formatModelDisplayName(baseId, repItem?.displayName),
@@ -537,6 +564,7 @@ function synthesizeDynamicModel(baseId, items) {
537
564
  thinkingLevelMap,
538
565
  input: repItem?.supportsImages ? ["text", "image"] : ["text"],
539
566
  cost: estimateModelCost(baseId),
567
+ promptCache,
540
568
  contextWindow: repItem?.maxTokens || defaultContext,
541
569
  maxTokens: repItem?.maxOutputTokens || defaultMaxOutput
542
570
  };
@@ -1384,6 +1412,7 @@ var QuotaStatusCoordinator = class {
1384
1412
 
1385
1413
  // src/stream.ts
1386
1414
  import {
1415
+ calculateCost,
1387
1416
  createAssistantMessageEventStream
1388
1417
  } from "@earendil-works/pi-ai";
1389
1418
 
@@ -1801,6 +1830,10 @@ function processLine(rawLine, state, onEvent) {
1801
1830
  } catch {
1802
1831
  return;
1803
1832
  }
1833
+ const streamError = payload?.error ?? payload?.response?.error;
1834
+ if (streamError) {
1835
+ throw new Error(streamError.message ?? JSON.stringify(streamError));
1836
+ }
1804
1837
  const response = payload.response || payload;
1805
1838
  if (response.responseId && !state.responseId) {
1806
1839
  state.responseId = response.responseId;
@@ -1821,8 +1854,14 @@ function processLine(rawLine, state, onEvent) {
1821
1854
  if (!candidate) return;
1822
1855
  if (candidate.finishReason === "MAX_TOKENS") {
1823
1856
  state.stopReason = "length";
1824
- } else if (typeof candidate.finishReason === "string" && candidate.finishReason !== "STOP") {
1857
+ state.rawStopReason = "MAX_TOKENS";
1858
+ } else if (candidate.finishReason === "STOP") {
1859
+ if (state.stopReason === "pending") {
1860
+ state.stopReason = "stop";
1861
+ }
1862
+ } else if (typeof candidate.finishReason === "string") {
1825
1863
  state.stopReason = "error";
1864
+ state.rawStopReason = candidate.finishReason;
1826
1865
  }
1827
1866
  const parts = candidate.content?.parts || [];
1828
1867
  for (const part of parts) {
@@ -1851,7 +1890,9 @@ function processLine(rawLine, state, onEvent) {
1851
1890
  });
1852
1891
  } else if (part.functionCall) {
1853
1892
  closeOpenBlock(state, onEvent);
1854
- state.stopReason = "toolUse";
1893
+ if (state.stopReason === "pending" || state.stopReason === "stop") {
1894
+ state.stopReason = "toolUse";
1895
+ }
1855
1896
  const block = {
1856
1897
  type: "toolCall",
1857
1898
  id: part.functionCall.id || `call_${state.content.length}`,
@@ -1899,36 +1940,43 @@ function finishStream(state, onEvent) {
1899
1940
  closeOpenBlock(state, onEvent);
1900
1941
  attachLoneSignature(state.content, state.lastThoughtSignature);
1901
1942
  }
1902
- async function consumeAntigravityStream(streamBody, output, stream, modelCost) {
1943
+ var NON_OVERFLOW_PATTERN = /rate limit|too many requests|quota exceeded|resource_exhausted/i;
1944
+ var CONTEXT_OVERFLOW_PATTERN = /(?:(?:input|prompt) token count.*exceeds|prompt (?:is )?too long)/i;
1945
+ function normalizeOverflowError(rawMessage) {
1946
+ if (rawMessage.includes("context_length_exceeded")) return rawMessage;
1947
+ if (!NON_OVERFLOW_PATTERN.test(rawMessage) && CONTEXT_OVERFLOW_PATTERN.test(rawMessage)) {
1948
+ return `context_length_exceeded: ${rawMessage}`;
1949
+ }
1950
+ return rawMessage;
1951
+ }
1952
+ async function consumeAntigravityStream(streamBody, output, stream, model, signal) {
1903
1953
  const state = {
1904
1954
  content: output.content,
1905
1955
  usage: { input: 0, output: 0, cacheRead: 0, reasoning: 0, total: 0 },
1906
- stopReason: "stop",
1956
+ stopReason: "pending",
1907
1957
  openType: null,
1908
1958
  buffer: ""
1909
1959
  };
1960
+ let lastTotalTokens = -1;
1910
1961
  const updateUsageAndCost = () => {
1911
1962
  output.usage.input = state.usage.input;
1912
1963
  output.usage.cacheRead = state.usage.cacheRead;
1913
1964
  output.usage.output = state.usage.output;
1914
1965
  output.usage.reasoning = state.usage.reasoning;
1915
1966
  output.usage.totalTokens = state.usage.total;
1916
- if (modelCost) {
1917
- const inputCost = output.usage.input * (modelCost.input || 0) / 1e6;
1918
- const outputCost = output.usage.output * (modelCost.output || 0) / 1e6;
1919
- const cacheCost = output.usage.cacheRead * (modelCost.cacheRead || 0) / 1e6;
1920
- output.usage.cost = {
1921
- input: inputCost,
1922
- output: outputCost,
1923
- cacheRead: cacheCost,
1924
- cacheWrite: 0,
1925
- total: inputCost + outputCost + cacheCost
1926
- };
1967
+ if (state.usage.total !== lastTotalTokens) {
1968
+ lastTotalTokens = state.usage.total;
1969
+ if (model.cost) {
1970
+ calculateCost(model, output.usage);
1971
+ }
1927
1972
  }
1928
1973
  if (state.responseId) {
1929
1974
  output.responseId ||= state.responseId;
1930
1975
  }
1931
1976
  output.stopReason = state.stopReason;
1977
+ if (state.rawStopReason) {
1978
+ output.rawStopReason = state.rawStopReason;
1979
+ }
1932
1980
  };
1933
1981
  const dispatchEvent = (ev) => {
1934
1982
  updateUsageAndCost();
@@ -1936,13 +1984,20 @@ async function consumeAntigravityStream(streamBody, output, stream, modelCost) {
1936
1984
  };
1937
1985
  const reader = streamBody.getReader();
1938
1986
  const decoder = new TextDecoder();
1939
- while (true) {
1940
- const { done, value } = await reader.read();
1941
- if (done) break;
1942
- feedChunk(decoder.decode(value, { stream: true }), state, dispatchEvent);
1987
+ try {
1988
+ while (true) {
1989
+ if (signal?.aborted) {
1990
+ throw new Error("Request was aborted");
1991
+ }
1992
+ const { done, value } = await reader.read();
1993
+ if (done) break;
1994
+ feedChunk(decoder.decode(value, { stream: true }), state, dispatchEvent);
1995
+ }
1996
+ finishStream(state, dispatchEvent);
1997
+ updateUsageAndCost();
1998
+ } finally {
1999
+ reader.releaseLock();
1943
2000
  }
1944
- finishStream(state, dispatchEvent);
1945
- updateUsageAndCost();
1946
2001
  }
1947
2002
  function streamAntigravity(model, context, options, catalog) {
1948
2003
  const stream = createAssistantMessageEventStream();
@@ -1970,7 +2025,7 @@ function streamAntigravity(model, context, options, catalog) {
1970
2025
  const effort = options?.reasoning;
1971
2026
  const trajectoryId = options && "trajectoryId" in options && typeof options.trajectoryId === "string" ? options.trajectoryId : void 0;
1972
2027
  const plan = catalog.resolvePlan(model.id, effort);
1973
- const requestBody = buildAntigravityRequestBody({
2028
+ let requestBody = buildAntigravityRequestBody({
1974
2029
  projectId,
1975
2030
  plan,
1976
2031
  context,
@@ -1979,13 +2034,51 @@ function streamAntigravity(model, context, options, catalog) {
1979
2034
  maxOutputTokens: options?.maxTokens ?? model.maxTokens,
1980
2035
  toolChoice: options?.toolChoice
1981
2036
  });
1982
- const streamBody = await postAntigravityStream({
2037
+ if (options?.onPayload) {
2038
+ const replacement = await options.onPayload(requestBody, model);
2039
+ if (replacement !== void 0) {
2040
+ requestBody = replacement;
2041
+ }
2042
+ }
2043
+ const endpoint = model.baseUrl || DEFAULT_ENDPOINT;
2044
+ const mergedHeaders = {};
2045
+ if (model.headers) {
2046
+ for (const [k, v] of Object.entries(model.headers)) {
2047
+ if (typeof v === "string") mergedHeaders[k] = v;
2048
+ }
2049
+ }
2050
+ if (options?.headers) {
2051
+ for (const [k, v] of Object.entries(options.headers)) {
2052
+ if (typeof v === "string") mergedHeaders[k] = v;
2053
+ }
2054
+ }
2055
+ const { response, stream: streamBody } = await postAntigravityStream({
1983
2056
  auth: token,
2057
+ endpoint,
1984
2058
  path: "v1internal:streamGenerateContent?alt=sse",
2059
+ headers: mergedHeaders,
1985
2060
  body: requestBody,
1986
2061
  signal: options?.signal
1987
2062
  });
1988
- await consumeAntigravityStream(streamBody, output, stream, model.cost);
2063
+ if (options?.onResponse) {
2064
+ const resHeaders = {};
2065
+ response.headers.forEach((val, key) => {
2066
+ resHeaders[key] = val;
2067
+ });
2068
+ await options.onResponse({ status: response.status, headers: resHeaders }, model);
2069
+ }
2070
+ await consumeAntigravityStream(streamBody, output, stream, model, options?.signal);
2071
+ if (options?.signal?.aborted) {
2072
+ throw new Error("Request was aborted");
2073
+ }
2074
+ if (output.stopReason === "pending") {
2075
+ throw new Error("Provider stream ended without a stop reason");
2076
+ }
2077
+ if (output.stopReason === "error" || output.stopReason === "aborted") {
2078
+ throw new Error(
2079
+ output.rawStopReason ? `Provider stopped with: ${output.rawStopReason}` : output.errorMessage || "An unknown error occurred"
2080
+ );
2081
+ }
1989
2082
  const doneReason = output.stopReason === "toolUse" || output.stopReason === "length" ? output.stopReason : "stop";
1990
2083
  stream.push({
1991
2084
  type: "done",
@@ -1995,7 +2088,8 @@ function streamAntigravity(model, context, options, catalog) {
1995
2088
  stream.end();
1996
2089
  } catch (error) {
1997
2090
  output.stopReason = options?.signal?.aborted ? "aborted" : "error";
1998
- output.errorMessage = error instanceof Error ? error.message : String(error);
2091
+ const rawMessage = error instanceof Error ? error.message : String(error);
2092
+ output.errorMessage = normalizeOverflowError(rawMessage);
1999
2093
  stream.push({ type: "error", reason: output.stopReason, error: output });
2000
2094
  stream.end();
2001
2095
  }
@@ -2293,6 +2387,7 @@ function index_default(pi) {
2293
2387
  // Purely dynamic provider per Pi SDK architecture
2294
2388
  oauth: {
2295
2389
  name: PROVIDER_NAME,
2390
+ isSubscription: true,
2296
2391
  login: loginAntigravity,
2297
2392
  refreshToken: refreshAntigravityToken,
2298
2393
  getApiKey