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 +26 -9
- package/package.json +1 -1
- package/src/constants.ts +5 -0
- package/src/plugin/project.ts +15 -43
- package/src/plugin/request.ts +116 -65
- package/src/plugin.ts +0 -7
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": {
|
|
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": {
|
|
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": {
|
|
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": {
|
|
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": {
|
|
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": {
|
|
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
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",
|
package/src/plugin/project.ts
CHANGED
|
@@ -2,7 +2,7 @@ import {
|
|
|
2
2
|
ANTIGRAVITY_HEADERS,
|
|
3
3
|
ANTIGRAVITY_ENDPOINT_FALLBACKS,
|
|
4
4
|
ANTIGRAVITY_LOAD_ENDPOINTS,
|
|
5
|
-
|
|
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
|
|
279
|
-
const
|
|
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
|
|
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:
|
|
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
|
-
|
|
313
|
-
const
|
|
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 (
|
|
316
|
-
|
|
301
|
+
if (resolvedManagedProjectId) {
|
|
302
|
+
return persistManagedProject(resolvedManagedProjectId);
|
|
317
303
|
}
|
|
318
304
|
|
|
319
|
-
|
|
320
|
-
|
|
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
|
-
|
|
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) {
|
package/src/plugin/request.ts
CHANGED
|
@@ -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
|
-
*
|
|
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 =
|
|
125
|
-
const upstreamModel =
|
|
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,
|
|
163
|
+
// Normalize tools. For Claude models, keep full function declarations (names + schemas).
|
|
191
164
|
if (Array.isArray(requestPayload.tools)) {
|
|
192
165
|
if (isClaudeModel) {
|
|
193
|
-
|
|
194
|
-
const
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
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
|
-
|
|
203
|
-
tool.custom
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
|
|
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:
|