opencode-antigravity-auth 1.0.0 → 1.0.2

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
@@ -17,7 +17,7 @@ Enable Opencode to authenticate against **Antigravity** (Google's IDE) via OAuth
17
17
 
18
18
  ```json
19
19
  {
20
- "plugin": ["opencode-antigravity-auth"]
20
+ "plugin": ["opencode-antigravity-auth@1.0.2"]
21
21
  }
22
22
  ```
23
23
 
@@ -30,7 +30,6 @@ Enable Opencode to authenticate against **Antigravity** (Google's IDE) via OAuth
30
30
  3) **Declare the models you want**
31
31
 
32
32
  Add Antigravity models under the `provider.google.models` section of your config:
33
-
34
33
  ```json
35
34
  {
36
35
  "plugin": ["opencode-antigravity-auth"],
@@ -39,27 +38,45 @@ Add Antigravity models under the `provider.google.models` section of your config
39
38
  "models": {
40
39
  "gemini-3-pro-high": {
41
40
  "name": "Gemini 3 Pro High (Antigravity)",
42
- "limit": { "context": 1048576, "output": 65535 }
41
+ "limit": {
42
+ "context": 1048576,
43
+ "output": 65535
44
+ }
43
45
  },
44
46
  "gemini-3-pro-low": {
45
47
  "name": "Gemini 3 Pro Low (Antigravity)",
46
- "limit": { "context": 1048576, "output": 65535 }
48
+ "limit": {
49
+ "context": 1048576,
50
+ "output": 65535
51
+ }
47
52
  },
48
53
  "claude-sonnet-4-5": {
49
54
  "name": "Claude Sonnet 4.5 (Antigravity)",
50
- "limit": { "context": 200000, "output": 64000 }
55
+ "limit": {
56
+ "context": 200000,
57
+ "output": 64000
58
+ }
51
59
  },
52
60
  "claude-sonnet-4-5-thinking": {
53
61
  "name": "Claude Sonnet 4.5 Thinking (Antigravity)",
54
- "limit": { "context": 200000, "output": 64000 }
62
+ "limit": {
63
+ "context": 200000,
64
+ "output": 64000
65
+ }
55
66
  },
56
67
  "claude-opus-4-5-thinking": {
57
68
  "name": "Claude Opus 4.5 Thinking (Antigravity)",
58
- "limit": { "context": 200000, "output": 64000 }
69
+ "limit": {
70
+ "context": 200000,
71
+ "output": 64000
72
+ }
59
73
  },
60
74
  "gpt-oss-120b-medium": {
61
75
  "name": "GPT-OSS 120B Medium (Antigravity)",
62
- "limit": { "context": 131072, "output": 32768 }
76
+ "limit": {
77
+ "context": 131072,
78
+ "output": 32768
79
+ }
63
80
  }
64
81
  }
65
82
  }
@@ -123,4 +140,4 @@ Use at your own risk. Proceed only if you understand and accept these risks.
123
140
  ## Credits
124
141
 
125
142
  - Inspired by and different from [opencode-gemini-auth](https://github.com/jenslys/opencode-gemini-auth) by [jenslys](https://github.com/jenslys). Thanks for the groundwork! 🚀
126
- - Thanks to [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) for the inspiration.
143
+ - Thanks to [CLIProxyAPI](https://github.com/router-for-me/CLIProxyAPI) for the inspiration.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "opencode-antigravity-auth",
3
3
  "module": "index.ts",
4
- "version": "1.0.0",
4
+ "version": "1.0.2",
5
5
  "author": "noefabris",
6
6
  "repository": "https://github.com/NoeFabris/opencode-antigravity-auth",
7
7
  "files": [
package/src/constants.ts CHANGED
@@ -58,6 +58,11 @@ export const ANTIGRAVITY_LOAD_ENDPOINTS = [
58
58
  */
59
59
  export const ANTIGRAVITY_ENDPOINT = ANTIGRAVITY_ENDPOINT_DAILY;
60
60
 
61
+ /**
62
+ * Hardcoded project id used when Antigravity does not return one (e.g., business/workspace accounts).
63
+ */
64
+ export const ANTIGRAVITY_DEFAULT_PROJECT_ID = "rising-fact-p41fc";
65
+
61
66
  export const ANTIGRAVITY_HEADERS = {
62
67
  "User-Agent": "antigravity/1.11.5 windows/amd64",
63
68
  "X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
@@ -2,7 +2,7 @@ import {
2
2
  ANTIGRAVITY_HEADERS,
3
3
  ANTIGRAVITY_ENDPOINT_FALLBACKS,
4
4
  ANTIGRAVITY_LOAD_ENDPOINTS,
5
- ANTIGRAVITY_PROVIDER_ID,
5
+ ANTIGRAVITY_DEFAULT_PROJECT_ID,
6
6
  } from "../constants";
7
7
  import { formatRefreshParts, parseRefreshParts } from "./auth";
8
8
  import type {
@@ -275,16 +275,14 @@ export async function ensureProjectContext(
275
275
  return { auth, effectiveProjectId: parts.managedProjectId };
276
276
  }
277
277
 
278
- const loadPayload = await loadManagedProject(accessToken, parts.projectId);
279
- const resolvedManagedProjectId = extractManagedProjectId(loadPayload);
280
-
281
- if (resolvedManagedProjectId) {
278
+ const fallbackProjectId = ANTIGRAVITY_DEFAULT_PROJECT_ID;
279
+ const persistManagedProject = async (managedProjectId: string): Promise<ProjectContextResult> => {
282
280
  const updatedAuth: OAuthAuthDetails = {
283
281
  ...auth,
284
282
  refresh: formatRefreshParts({
285
283
  refreshToken: parts.refreshToken,
286
284
  projectId: parts.projectId,
287
- managedProjectId: resolvedManagedProjectId,
285
+ managedProjectId,
288
286
  }),
289
287
  };
290
288
 
@@ -293,49 +291,23 @@ export async function ensureProjectContext(
293
291
  body: updatedAuth,
294
292
  });
295
293
 
296
- return { auth: updatedAuth, effectiveProjectId: resolvedManagedProjectId };
297
- }
298
-
299
- if (parts.projectId) {
300
- return { auth, effectiveProjectId: parts.projectId };
301
- }
302
-
303
- if (!loadPayload) {
304
- throw new ProjectIdRequiredError();
305
- }
306
-
307
- const currentTierId = loadPayload.currentTier?.id ?? undefined;
308
- if (currentTierId && currentTierId !== "FREE") {
309
- throw new ProjectIdRequiredError();
310
- }
294
+ return { auth: updatedAuth, effectiveProjectId: managedProjectId };
295
+ };
311
296
 
312
- const defaultTierId = getDefaultTierId(loadPayload.allowedTiers);
313
- const tierId = defaultTierId ?? "FREE";
297
+ // Try to resolve a managed project from Antigravity if possible.
298
+ const loadPayload = await loadManagedProject(accessToken, parts.projectId ?? fallbackProjectId);
299
+ const resolvedManagedProjectId = extractManagedProjectId(loadPayload);
314
300
 
315
- if (tierId !== "FREE") {
316
- throw new ProjectIdRequiredError();
301
+ if (resolvedManagedProjectId) {
302
+ return persistManagedProject(resolvedManagedProjectId);
317
303
  }
318
304
 
319
- const onboardedProjectId = await onboardManagedProject(accessToken, tierId, parts.projectId);
320
- if (onboardedProjectId) {
321
- const updatedAuth: OAuthAuthDetails = {
322
- ...auth,
323
- refresh: formatRefreshParts({
324
- refreshToken: parts.refreshToken,
325
- projectId: parts.projectId,
326
- managedProjectId: onboardedProjectId,
327
- }),
328
- };
329
-
330
- await client.auth.set({
331
- path: { id: providerId },
332
- body: updatedAuth,
333
- });
334
-
335
- return { auth: updatedAuth, effectiveProjectId: onboardedProjectId };
305
+ if (parts.projectId) {
306
+ return { auth, effectiveProjectId: parts.projectId };
336
307
  }
337
308
 
338
- throw new ProjectIdRequiredError();
309
+ // No project id present in auth; fall back to the hardcoded id for requests.
310
+ return { auth, effectiveProjectId: fallbackProjectId };
339
311
  };
340
312
 
341
313
  if (!cacheKey) {
@@ -23,35 +23,8 @@ function generateSyntheticProjectId(): string {
23
23
  }
24
24
 
25
25
  const STREAM_ACTION = "streamGenerateContent";
26
- const MODEL_FALLBACKS: Record<string, string> = {
27
- "gemini-2.5-flash-image": "gemini-2.5-flash",
28
- "gemini-2.5-flash": "gemini-3-pro-preview",
29
- "models/gemini-2.5-flash": "gemini-3-pro-preview",
30
- "gemini-3-pro-high": "gemini-3-pro-preview",
31
- "gemini-3-pro-low": "gemini-3-pro-preview",
32
- "claude-sonnet-4-5": "claude-4-5-sonnet",
33
- "claude-sonnet-4-5-thinking": "claude-4-5-sonnet-thinking",
34
- "claude-opus-4-5-thinking": "claude-4-5-opus-thinking",
35
- "gpt-oss-120b-medium": "gpt-oss-120b-medium",
36
- };
37
-
38
- // Maps friendly/alias names to the upstream model IDs Antigravity expects.
39
- const MODEL_UPSTREAM_ALIASES: Record<string, string> = {
40
- "gemini-2.5-computer-use-preview-10-2025": "rev19-uic3-1p",
41
- "gemini-3-pro-image-preview": "gemini-3-pro-image",
42
- "gemini-3-pro-preview": "gemini-3-pro-high",
43
- "gemini-claude-sonnet-4-5": "claude-sonnet-4-5",
44
- "gemini-claude-sonnet-4-5-thinking": "claude-sonnet-4-5-thinking",
45
- "gemini-claude-opus-4-5-thinking": "claude-opus-4-5-thinking",
46
- // Anthropic model name normalization (order expected by Antigravity)
47
- "claude-4-5-sonnet": "claude-sonnet-4-5",
48
- "claude-4-5-sonnet-thinking": "claude-sonnet-4-5-thinking",
49
- "claude-4-5-opus-thinking": "claude-opus-4-5-thinking",
50
- };
51
-
52
26
  /**
53
- * Endpoint fallback order (daily autopush prod)
54
- * Matches CLIProxy and Vibeproxy behavior
27
+ * Detects requests headed to the Google Generative Language API so we can intercept them.
55
28
  */
56
29
  export function isGenerativeLanguageRequest(input: RequestInfo): input is string {
57
30
  return typeof input === "string" && input.includes("generativelanguage.googleapis.com");
@@ -121,8 +94,8 @@ export function prepareAntigravityRequest(
121
94
  }
122
95
 
123
96
  const [, rawModel = "", rawAction = ""] = match;
124
- const effectiveModel = MODEL_FALLBACKS[rawModel] ?? rawModel;
125
- const upstreamModel = MODEL_UPSTREAM_ALIASES[effectiveModel] ?? effectiveModel;
97
+ const effectiveModel = rawModel;
98
+ const upstreamModel = rawModel;
126
99
  const streaming = rawAction === STREAM_ACTION;
127
100
  const baseEndpoint = endpointOverride ?? ANTIGRAVITY_ENDPOINT;
128
101
  const transformedUrl = `${baseEndpoint}/v1internal:${rawAction}${
@@ -187,41 +160,86 @@ export function prepareAntigravityRequest(
187
160
  }
188
161
  }
189
162
 
190
- // Normalize tools. For Claude models, send functionDeclarations with parameters (no custom).
163
+ // Normalize tools. For Claude models, keep full function declarations (names + schemas).
191
164
  if (Array.isArray(requestPayload.tools)) {
192
165
  if (isClaudeModel) {
193
- // Use functionDeclarations with parameters (mirrors CLIProxy path that Antigravity accepts).
194
- const claudeTools: any[] = requestPayload.tools.map((tool: any, idx: number) => {
195
- const schema =
196
- tool.function?.parameters ||
197
- tool.function?.input_schema ||
198
- tool.function?.inputSchema ||
166
+ const functionDeclarations: any[] = [];
167
+ const passthroughTools: any[] = [];
168
+
169
+ const normalizeSchema = (schema: any) => {
170
+ if (schema && typeof schema === "object") {
171
+ return schema;
172
+ }
173
+ toolDebugMissing += 1;
174
+ return { type: "object", properties: {} };
175
+ };
176
+
177
+ requestPayload.tools.forEach((tool: any, idx: number) => {
178
+ const pushDeclaration = (decl: any, source: string) => {
179
+ const schema =
180
+ decl?.parameters ||
181
+ decl?.input_schema ||
182
+ decl?.inputSchema ||
183
+ tool.parameters ||
184
+ tool.input_schema ||
185
+ tool.inputSchema ||
186
+ tool.function?.parameters ||
187
+ tool.function?.input_schema ||
188
+ tool.function?.inputSchema ||
189
+ tool.custom?.parameters ||
190
+ tool.custom?.input_schema;
191
+
192
+ const name =
193
+ decl?.name ||
194
+ tool.name ||
195
+ tool.function?.name ||
196
+ tool.custom?.name ||
197
+ `tool-${functionDeclarations.length}`;
198
+
199
+ const description =
200
+ decl?.description ||
201
+ tool.description ||
202
+ tool.function?.description ||
203
+ tool.custom?.description ||
204
+ "";
205
+
206
+ functionDeclarations.push({
207
+ name,
208
+ description,
209
+ parameters: normalizeSchema(schema),
210
+ });
211
+
212
+ toolDebugSummaries.push(
213
+ `decl=${name},src=${source},hasSchema=${schema ? "y" : "n"}`,
214
+ );
215
+ };
216
+
217
+ if (Array.isArray(tool.functionDeclarations) && tool.functionDeclarations.length > 0) {
218
+ tool.functionDeclarations.forEach((decl: any) => pushDeclaration(decl, "functionDeclarations"));
219
+ return;
220
+ }
221
+
222
+ // Fall back to function/custom style definitions.
223
+ if (
224
+ tool.function ||
225
+ tool.custom ||
199
226
  tool.parameters ||
200
227
  tool.input_schema ||
201
- tool.inputSchema ||
202
- tool.custom?.parameters ||
203
- tool.custom?.input_schema ||
204
- { type: "object", properties: {} };
205
- const name =
206
- tool.name ||
207
- tool.function?.name ||
208
- tool.custom?.name ||
209
- `tool-${idx}`;
210
- const description =
211
- tool.description || tool.function?.description || tool.custom?.description || "";
212
-
213
- return {
214
- functionDeclarations: [
215
- {
216
- name,
217
- description,
218
- parameters: schema,
219
- },
220
- ],
221
- };
228
+ tool.inputSchema
229
+ ) {
230
+ pushDeclaration(tool.function ?? tool.custom ?? tool, "function/custom");
231
+ return;
232
+ }
233
+
234
+ // Preserve any non-function tool entries (e.g., codeExecution) untouched.
235
+ passthroughTools.push(tool);
222
236
  });
223
237
 
224
- requestPayload.tools = claudeTools;
238
+ const finalTools: any[] = [];
239
+ if (functionDeclarations.length > 0) {
240
+ finalTools.push({ functionDeclarations });
241
+ }
242
+ requestPayload.tools = finalTools.concat(passthroughTools);
225
243
  } else {
226
244
  // Default normalization for non-Claude models
227
245
  requestPayload.tools = requestPayload.tools.map((tool: any, toolIndex: number) => {
@@ -290,6 +308,45 @@ export function prepareAntigravityRequest(
290
308
  }
291
309
  }
292
310
 
311
+ // For Claude models, ensure functionCall/tool use parts carry IDs (required by Anthropic).
312
+ if (isClaudeModel && Array.isArray(requestPayload.contents)) {
313
+ let toolCallCounter = 0;
314
+ const lastCallIdByName = new Map<string, string>();
315
+
316
+ requestPayload.contents = requestPayload.contents.map((content: any) => {
317
+ if (!content || !Array.isArray(content.parts)) {
318
+ return content;
319
+ }
320
+
321
+ const newParts = content.parts.map((part: any) => {
322
+ if (part && typeof part === "object" && part.functionCall) {
323
+ const call = { ...part.functionCall };
324
+ if (!call.id) {
325
+ call.id = `tool-call-${++toolCallCounter}`;
326
+ }
327
+ const nameKey = typeof call.name === "string" ? call.name : `tool-${toolCallCounter}`;
328
+ lastCallIdByName.set(nameKey, call.id);
329
+ return { ...part, functionCall: call };
330
+ }
331
+
332
+ if (part && typeof part === "object" && part.functionResponse) {
333
+ const resp = { ...part.functionResponse };
334
+ if (!resp.id && typeof resp.name === "string") {
335
+ const linkedId = lastCallIdByName.get(resp.name);
336
+ if (linkedId) {
337
+ resp.id = linkedId;
338
+ }
339
+ }
340
+ return { ...part, functionResponse: resp };
341
+ }
342
+
343
+ return part;
344
+ });
345
+
346
+ return { ...content, parts: newParts };
347
+ });
348
+ }
349
+
293
350
  if ("model" in requestPayload) {
294
351
  delete requestPayload.model;
295
352
  }
@@ -313,12 +370,6 @@ export function prepareAntigravityRequest(
313
370
  }
314
371
 
315
372
  body = JSON.stringify(wrappedBody);
316
-
317
- if (wrappedBody.request && typeof wrappedBody.request === 'object') {
318
- (wrappedBody.request as any).sessionId = "-" + Math.floor(Math.random() * 9000000000000000000).toString();
319
- }
320
-
321
- body = JSON.stringify(wrappedBody);
322
373
  }
323
374
  } catch (error) {
324
375
  throw error;
package/src/plugin.ts CHANGED
@@ -222,7 +222,6 @@ export const createAntigravityPlugin = (providerId: string) => async (
222
222
  label: "OAuth with Google (Antigravity)",
223
223
  type: "oauth",
224
224
  authorize: async () => {
225
- console.log("\n=== Google Antigravity OAuth Setup ===");
226
225
 
227
226
  const isHeadless = !!(
228
227
  process.env.SSH_CONNECTION ||
@@ -252,7 +251,6 @@ export const createAntigravityPlugin = (providerId: string) => async (
252
251
  } else {
253
252
  exec(`xdg-open "${authorization.url}"`);
254
253
  }
255
- console.log("Opening your browser to authenticate...");
256
254
  } catch (e) {
257
255
  console.log("Could not open browser automatically. Please Copy/Paste the URL below.");
258
256
  }
@@ -260,8 +258,6 @@ export const createAntigravityPlugin = (providerId: string) => async (
260
258
 
261
259
  if (listener) {
262
260
  const { host } = new URL(ANTIGRAVITY_REDIRECT_URI);
263
- console.log(`1. We'll automatically capture the browser redirect on http://${host}.`);
264
- console.log("2. Once you see the 'Authentication complete' page in your browser, return to this terminal.\n");
265
261
 
266
262
  return {
267
263
  url: authorization.url,
@@ -300,9 +296,6 @@ export const createAntigravityPlugin = (providerId: string) => async (
300
296
  };
301
297
  }
302
298
 
303
- console.log("1. Visit the URL below to sign in.");
304
- console.log("2. Copy the full redirected URL (localhost) and paste it back here.\n");
305
-
306
299
  return {
307
300
  url: authorization.url,
308
301
  instructions: