opencode-google-auth 0.0.2 → 0.0.3
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/package.json +10 -10
- package/src/antigravity-spoof.txt +1 -0
- package/src/lib/logger.ts +5 -2
- package/src/main.ts +5 -0
- package/src/services/config.ts +29 -90
- package/src/services/oauth.ts +11 -0
- package/src/transform/request.ts +2 -3
- package/src/models.json +0 -155
- package/src/services/config.test.ts +0 -194
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-google-auth",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.3",
|
|
4
4
|
"description": "_description_",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"opencode-google-auth"
|
|
@@ -26,9 +26,7 @@
|
|
|
26
26
|
"src"
|
|
27
27
|
],
|
|
28
28
|
"scripts": {
|
|
29
|
-
"
|
|
30
|
-
"predeploy": "bun run build",
|
|
31
|
-
"deploy": "bun ./scripts/deploy.ts",
|
|
29
|
+
"deploy": "bun build ./src/main.ts --target bun --outfile .opencode/plugin/opencode-google-auth.js",
|
|
32
30
|
"format": "prettier --write .",
|
|
33
31
|
"lint": "oxlint",
|
|
34
32
|
"release": "bumpp && npm publish",
|
|
@@ -38,19 +36,21 @@
|
|
|
38
36
|
"@effect/experimental": "^0.58.0",
|
|
39
37
|
"@effect/platform": "^0.94.1",
|
|
40
38
|
"@effect/platform-bun": "^0.87.0",
|
|
41
|
-
"@opencode-ai/plugin": "^1.1.
|
|
39
|
+
"@opencode-ai/plugin": "^1.1.18",
|
|
42
40
|
"arkregex": "^0.0.5",
|
|
43
41
|
"effect": "^3.19.14",
|
|
44
|
-
"google-auth-library": "^10.5.0"
|
|
42
|
+
"google-auth-library": "^10.5.0",
|
|
43
|
+
"open": "^11.0.0",
|
|
44
|
+
"xdg-basedir": "^5.1.0"
|
|
45
45
|
},
|
|
46
46
|
"devDependencies": {
|
|
47
47
|
"@ai-sdk/google": "^3.0.7",
|
|
48
|
-
"@effect/language-service": "^0.
|
|
48
|
+
"@effect/language-service": "^0.65.0",
|
|
49
49
|
"@types/bun": "^1.3.5",
|
|
50
50
|
"@types/yargs": "^17.0.35",
|
|
51
|
-
"@typescript/native-preview": "^7.0.0-dev.
|
|
52
|
-
"ai": "^6.0.
|
|
53
|
-
"bumpp": "^10.
|
|
51
|
+
"@typescript/native-preview": "^7.0.0-dev.20260113.1",
|
|
52
|
+
"ai": "^6.0.33",
|
|
53
|
+
"bumpp": "^10.4.0",
|
|
54
54
|
"oxlint": "^1.39.0",
|
|
55
55
|
"oxlint-tsgolint": "^0.11.0",
|
|
56
56
|
"prettier": "^3.7.4",
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
You are Antigravity, a powerful agentic AI coding assistant designed by the Google Deepmind team working on Advanced Agentic Coding.You are pair programming with a USER to solve their coding task. The task may require creating a new codebase, modifying or debugging an existing codebase, or simply answering a question.**Absolute paths only****Proactiveness**
|
package/src/lib/logger.ts
CHANGED
|
@@ -2,8 +2,9 @@ import { PlatformLogger } from "@effect/platform"
|
|
|
2
2
|
import { Effect, Inspectable, Logger, LogLevel, pipe } from "effect"
|
|
3
3
|
import path from "node:path"
|
|
4
4
|
import type { OpenCodeLogLevel } from "../types"
|
|
5
|
-
import { ProviderConfig } from "../services/config"
|
|
5
|
+
import { PLUGIN_NAME, ProviderConfig } from "../services/config"
|
|
6
6
|
import { OpenCodeContext } from "../services/opencode"
|
|
7
|
+
import { xdgData } from "xdg-basedir"
|
|
7
8
|
|
|
8
9
|
const makeOpenCodeLogger = Effect.gen(function* () {
|
|
9
10
|
const openCode = yield* OpenCodeContext
|
|
@@ -32,11 +33,13 @@ const makeOpenCodeLogger = Effect.gen(function* () {
|
|
|
32
33
|
})
|
|
33
34
|
})
|
|
34
35
|
|
|
36
|
+
const LOG_DIR = xdgData ? path.join(xdgData, "opencode") : import.meta.dir
|
|
37
|
+
|
|
35
38
|
export const combinedLogger = Effect.gen(function* () {
|
|
36
39
|
const openCodeLogger = yield* makeOpenCodeLogger
|
|
37
40
|
const fileLogger = yield* pipe(
|
|
38
41
|
Logger.jsonLogger,
|
|
39
|
-
PlatformLogger.toFile(path.join(
|
|
42
|
+
PlatformLogger.toFile(path.join(LOG_DIR, `${PLUGIN_NAME}.log`)),
|
|
40
43
|
)
|
|
41
44
|
|
|
42
45
|
return Logger.zip(openCodeLogger, fileLogger)
|
package/src/main.ts
CHANGED
|
@@ -17,6 +17,7 @@ import { transformRequest } from "./transform/request"
|
|
|
17
17
|
import { transformNonStreamingResponse } from "./transform/response"
|
|
18
18
|
import { transformStreamingResponse } from "./transform/stream"
|
|
19
19
|
import type { Credentials, ModelsDev } from "./types"
|
|
20
|
+
import antigravitySpoof from "./antigravity-spoof.txt"
|
|
20
21
|
|
|
21
22
|
const fetchModelsDev = Effect.gen(function* () {
|
|
22
23
|
const client = yield* HttpClient.HttpClient
|
|
@@ -251,6 +252,10 @@ export const antigravity: Plugin = async (context) => {
|
|
|
251
252
|
},
|
|
252
253
|
],
|
|
253
254
|
},
|
|
255
|
+
"experimental.chat.system.transform": async (_input, output) => {
|
|
256
|
+
// THIS IS REQUIRED OTHERWISE YOU'LL GET 429 OR 403 FOR SOME GODDAMN REASON
|
|
257
|
+
output.system.unshift(antigravitySpoof)
|
|
258
|
+
},
|
|
254
259
|
"chat.params": async (input, output) => {
|
|
255
260
|
await runtime.runPromise(
|
|
256
261
|
Effect.log("chat.params event before:", input.model, output.options),
|
package/src/services/config.ts
CHANGED
|
@@ -40,8 +40,8 @@ export class ProviderConfig extends Context.Tag("ProviderConfig")<
|
|
|
40
40
|
ProviderConfigShape
|
|
41
41
|
>() {}
|
|
42
42
|
|
|
43
|
+
export const PLUGIN_NAME = "opencode-google-auth"
|
|
43
44
|
export const CODE_ASSIST_VERSION = "v1internal"
|
|
44
|
-
|
|
45
45
|
export const CLIENT_METADATA = {
|
|
46
46
|
ideType: "IDE_UNSPECIFIED",
|
|
47
47
|
platform: "PLATFORM_UNSPECIFIED",
|
|
@@ -148,7 +148,12 @@ export const antigravityConfig = (): ProviderConfigShape => ({
|
|
|
148
148
|
"claude-opus-4-5@20251101"
|
|
149
149
|
] as OpenCodeModel
|
|
150
150
|
|
|
151
|
-
const models: Record<
|
|
151
|
+
const models: Record<
|
|
152
|
+
string,
|
|
153
|
+
OpenCodeModel & {
|
|
154
|
+
variants?: Record<string, Record<string, unknown>>
|
|
155
|
+
}
|
|
156
|
+
> = {
|
|
152
157
|
"gemini-3-flash": {
|
|
153
158
|
...geminiFlash,
|
|
154
159
|
id: "gemini-3-flash",
|
|
@@ -157,7 +162,6 @@ export const antigravityConfig = (): ProviderConfigShape => ({
|
|
|
157
162
|
...geminiPro,
|
|
158
163
|
id: "gemini-3-pro-low",
|
|
159
164
|
name: "Gemini 3 Pro (Low)",
|
|
160
|
-
temperature: false,
|
|
161
165
|
options: {
|
|
162
166
|
thinkingConfig: {
|
|
163
167
|
thinkingLevel: "low",
|
|
@@ -188,12 +192,12 @@ export const antigravityConfig = (): ProviderConfigShape => ({
|
|
|
188
192
|
"claude-sonnet-4-5-thinking": {
|
|
189
193
|
...claudeSonnet,
|
|
190
194
|
id: "claude-sonnet-4-5-thinking",
|
|
191
|
-
name: "Claude Sonnet 4.5 (
|
|
195
|
+
name: "Claude Sonnet 4.5 (Thinking)",
|
|
192
196
|
},
|
|
193
197
|
"claude-opus-4-5-thinking": {
|
|
194
198
|
...claudeOpus,
|
|
195
199
|
id: "claude-opus-4-5-thinking",
|
|
196
|
-
name: "Claude Opus 4.5 (
|
|
200
|
+
name: "Claude Opus 4.5 (Thinking)",
|
|
197
201
|
},
|
|
198
202
|
}
|
|
199
203
|
|
|
@@ -238,6 +242,24 @@ export const antigravityConfig = (): ProviderConfigShape => ({
|
|
|
238
242
|
| Record<string, unknown>
|
|
239
243
|
| undefined
|
|
240
244
|
|
|
245
|
+
const tools = request.tools as
|
|
246
|
+
| Array<{ functionDeclarations?: Array<{ parameters?: unknown }> }>
|
|
247
|
+
| undefined
|
|
248
|
+
if (tools && Array.isArray(tools)) {
|
|
249
|
+
for (const tool of tools) {
|
|
250
|
+
if (
|
|
251
|
+
tool.functionDeclarations
|
|
252
|
+
&& Array.isArray(tool.functionDeclarations)
|
|
253
|
+
) {
|
|
254
|
+
for (const func of tool.functionDeclarations) {
|
|
255
|
+
if (!func.parameters) {
|
|
256
|
+
func.parameters = { type: "object", properties: {} }
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
241
263
|
innerRequest.toolConfig = {
|
|
242
264
|
functionCallingConfig: {
|
|
243
265
|
mode: "VALIDATED",
|
|
@@ -278,101 +300,18 @@ export const antigravityConfig = (): ProviderConfigShape => ({
|
|
|
278
300
|
}
|
|
279
301
|
|
|
280
302
|
if (sessionId) {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
const finalSessionId = [
|
|
284
|
-
`-${crypto.randomUUID()}`,
|
|
285
|
-
body.model,
|
|
286
|
-
body.project,
|
|
287
|
-
`seed-${hashedSession}`,
|
|
288
|
-
].join(":")
|
|
289
|
-
|
|
290
|
-
innerRequest.sessionId = finalSessionId
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
if (innerRequest.tools && Array.isArray(innerRequest.tools)) {
|
|
294
|
-
const tools = innerRequest.tools as Array<Record<string, unknown>>
|
|
295
|
-
for (const tool of tools) {
|
|
296
|
-
if (
|
|
297
|
-
tool.functionDeclarations
|
|
298
|
-
&& Array.isArray(tool.functionDeclarations)
|
|
299
|
-
) {
|
|
300
|
-
const functionDeclarations = tool.functionDeclarations as Array<
|
|
301
|
-
Record<string, unknown>
|
|
302
|
-
>
|
|
303
|
-
for (let i = 0; i < functionDeclarations.length; i++) {
|
|
304
|
-
const declaration = functionDeclarations[i]
|
|
305
|
-
if (declaration && declaration.name === "todoread") {
|
|
306
|
-
functionDeclarations[i] = {
|
|
307
|
-
...functionDeclarations[i],
|
|
308
|
-
parameters: {
|
|
309
|
-
type: "object",
|
|
310
|
-
properties: {
|
|
311
|
-
_placeholder: {
|
|
312
|
-
type: "boolean",
|
|
313
|
-
description: "Placeholder. Always pass true.",
|
|
314
|
-
},
|
|
315
|
-
},
|
|
316
|
-
required: ["_placeholder"],
|
|
317
|
-
additionalProperties: false,
|
|
318
|
-
},
|
|
319
|
-
}
|
|
320
|
-
}
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
// if (
|
|
327
|
-
// innerRequest.systemInstruction
|
|
328
|
-
// && typeof innerRequest.systemInstruction === "object"
|
|
329
|
-
// ) {
|
|
330
|
-
// const systemInstruction = innerRequest.systemInstruction as Record<
|
|
331
|
-
// string,
|
|
332
|
-
// unknown
|
|
333
|
-
// >
|
|
334
|
-
|
|
335
|
-
// if (systemInstruction.parts && Array.isArray(systemInstruction.parts)) {
|
|
336
|
-
// let parts = systemInstruction.parts as Array<{ text: string }>
|
|
337
|
-
|
|
338
|
-
// parts.unshift({
|
|
339
|
-
// text: "You are Antigravity, a powerful agentic AI coding assistant designed by the Google DeepMind team working on Advanced Agentic Coding.",
|
|
340
|
-
// })
|
|
341
|
-
// }
|
|
342
|
-
// }
|
|
343
|
-
|
|
344
|
-
if (
|
|
345
|
-
innerRequest.systemInstruction
|
|
346
|
-
&& typeof innerRequest.systemInstruction === "object"
|
|
347
|
-
) {
|
|
348
|
-
const systemInstruction = innerRequest.systemInstruction as Record<
|
|
349
|
-
string,
|
|
350
|
-
unknown
|
|
351
|
-
>
|
|
352
|
-
|
|
353
|
-
systemInstruction.role = "user"
|
|
303
|
+
innerRequest.sessionId = sessionId
|
|
354
304
|
}
|
|
355
305
|
|
|
356
306
|
return {
|
|
357
307
|
headers,
|
|
358
308
|
url,
|
|
359
309
|
body: {
|
|
360
|
-
...body,
|
|
361
310
|
requestType: "agent",
|
|
362
311
|
userAgent: "antigravity",
|
|
363
312
|
requestId: `agent-${crypto.randomUUID()}`,
|
|
313
|
+
...body,
|
|
364
314
|
},
|
|
365
315
|
}
|
|
366
316
|
}),
|
|
367
317
|
})
|
|
368
|
-
|
|
369
|
-
async function hash(str: string) {
|
|
370
|
-
const encoder = new TextEncoder()
|
|
371
|
-
const data = encoder.encode(str)
|
|
372
|
-
|
|
373
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data)
|
|
374
|
-
const hashArray = Array.from(new Uint8Array(hashBuffer))
|
|
375
|
-
const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("")
|
|
376
|
-
|
|
377
|
-
return hashHex.slice(0, 16)
|
|
378
|
-
}
|
package/src/services/oauth.ts
CHANGED
|
@@ -16,6 +16,7 @@ import { Data, Deferred, Effect, Fiber, Schema } from "effect"
|
|
|
16
16
|
import { OAuth2Client } from "google-auth-library"
|
|
17
17
|
import type { BunServeOptions } from "../types"
|
|
18
18
|
import { ProviderConfig } from "./config"
|
|
19
|
+
import open from "open"
|
|
19
20
|
|
|
20
21
|
export class OAuthError extends Data.TaggedError("OAuthError")<{
|
|
21
22
|
readonly reason: "browser" | "callback" | "state_mismatch" | "token_exchange"
|
|
@@ -69,7 +70,17 @@ class OAuth extends Effect.Service<OAuth>()("OAuth", {
|
|
|
69
70
|
scope: config.SCOPES as unknown as string[],
|
|
70
71
|
prompt: "consent",
|
|
71
72
|
})
|
|
73
|
+
|
|
72
74
|
yield* Effect.log(`OAuth2 authorization URL: ${authUrl}`)
|
|
75
|
+
yield* Effect.tryPromise({
|
|
76
|
+
try: () => open(authUrl),
|
|
77
|
+
catch: (cause) =>
|
|
78
|
+
new OAuthError({
|
|
79
|
+
reason: "browser",
|
|
80
|
+
message: "Failed to open browser",
|
|
81
|
+
cause,
|
|
82
|
+
}),
|
|
83
|
+
})
|
|
73
84
|
|
|
74
85
|
const serverFiber = yield* HttpRouter.empty.pipe(
|
|
75
86
|
HttpRouter.get(
|
package/src/transform/request.ts
CHANGED
|
@@ -49,8 +49,7 @@ export const transformRequest = Effect.fn("transformRequest")(function* (
|
|
|
49
49
|
// Transform headers
|
|
50
50
|
const headers = new Headers(init?.headers)
|
|
51
51
|
headers.delete("x-api-key")
|
|
52
|
-
|
|
53
|
-
headers.set("x-opencode-tools-debug", "1")
|
|
52
|
+
headers.delete("x-goog-api-key")
|
|
54
53
|
headers.set("Authorization", `Bearer ${accessToken}`)
|
|
55
54
|
|
|
56
55
|
for (const [key, value] of Object.entries(config.HEADERS)) {
|
|
@@ -69,8 +68,8 @@ export const transformRequest = Effect.fn("transformRequest")(function* (
|
|
|
69
68
|
)
|
|
70
69
|
|
|
71
70
|
const wrappedBody = {
|
|
72
|
-
project: projectId,
|
|
73
71
|
model,
|
|
72
|
+
project: projectId,
|
|
74
73
|
request: parsedBody ?? {},
|
|
75
74
|
}
|
|
76
75
|
|
package/src/models.json
DELETED
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "google",
|
|
3
|
-
"env": ["GOOGLE_GENERATIVE_AI_API_KEY", "GEMINI_API_KEY"],
|
|
4
|
-
"npm": "@ai-sdk/google",
|
|
5
|
-
"name": "Google",
|
|
6
|
-
"doc": "https://ai.google.dev/gemini-api/docs/pricing",
|
|
7
|
-
"models": {
|
|
8
|
-
"gemini-3-flash-preview": {
|
|
9
|
-
"id": "gemini-3-flash-preview",
|
|
10
|
-
"name": "Gemini 3 Flash Preview",
|
|
11
|
-
"family": "gemini-flash",
|
|
12
|
-
"attachment": true,
|
|
13
|
-
"reasoning": true,
|
|
14
|
-
"tool_call": true,
|
|
15
|
-
"structured_output": true,
|
|
16
|
-
"temperature": true,
|
|
17
|
-
"knowledge": "2025-01",
|
|
18
|
-
"release_date": "2025-12-17",
|
|
19
|
-
"last_updated": "2025-12-17",
|
|
20
|
-
"modalities": {
|
|
21
|
-
"input": ["text", "image", "video", "audio", "pdf"],
|
|
22
|
-
"output": ["text"]
|
|
23
|
-
},
|
|
24
|
-
"open_weights": false,
|
|
25
|
-
"cost": {
|
|
26
|
-
"input": 0.5,
|
|
27
|
-
"output": 3,
|
|
28
|
-
"cache_read": 0.05,
|
|
29
|
-
"context_over_200k": {
|
|
30
|
-
"input": 0.5,
|
|
31
|
-
"output": 3,
|
|
32
|
-
"cache_read": 0.05
|
|
33
|
-
}
|
|
34
|
-
},
|
|
35
|
-
"limit": {
|
|
36
|
-
"context": 1048576,
|
|
37
|
-
"output": 65536
|
|
38
|
-
}
|
|
39
|
-
},
|
|
40
|
-
"gemini-3-pro-preview": {
|
|
41
|
-
"id": "gemini-3-pro-preview",
|
|
42
|
-
"name": "Gemini 3 Pro Preview",
|
|
43
|
-
"family": "gemini-pro",
|
|
44
|
-
"attachment": true,
|
|
45
|
-
"reasoning": true,
|
|
46
|
-
"tool_call": true,
|
|
47
|
-
"structured_output": true,
|
|
48
|
-
"temperature": true,
|
|
49
|
-
"knowledge": "2025-01",
|
|
50
|
-
"release_date": "2025-11-18",
|
|
51
|
-
"last_updated": "2025-11-18",
|
|
52
|
-
"modalities": {
|
|
53
|
-
"input": ["text", "image", "video", "audio", "pdf"],
|
|
54
|
-
"output": ["text"]
|
|
55
|
-
},
|
|
56
|
-
"open_weights": false,
|
|
57
|
-
"cost": {
|
|
58
|
-
"input": 2,
|
|
59
|
-
"output": 12,
|
|
60
|
-
"cache_read": 0.2,
|
|
61
|
-
"context_over_200k": {
|
|
62
|
-
"input": 4,
|
|
63
|
-
"output": 18,
|
|
64
|
-
"cache_read": 0.4
|
|
65
|
-
}
|
|
66
|
-
},
|
|
67
|
-
"limit": {
|
|
68
|
-
"context": 1000000,
|
|
69
|
-
"output": 64000
|
|
70
|
-
}
|
|
71
|
-
},
|
|
72
|
-
"gemini-2.5-flash": {
|
|
73
|
-
"id": "gemini-2.5-flash",
|
|
74
|
-
"name": "Gemini 2.5 Flash",
|
|
75
|
-
"family": "gemini-flash",
|
|
76
|
-
"attachment": true,
|
|
77
|
-
"reasoning": true,
|
|
78
|
-
"tool_call": true,
|
|
79
|
-
"structured_output": true,
|
|
80
|
-
"temperature": true,
|
|
81
|
-
"knowledge": "2025-01",
|
|
82
|
-
"release_date": "2025-03-20",
|
|
83
|
-
"last_updated": "2025-06-05",
|
|
84
|
-
"modalities": {
|
|
85
|
-
"input": ["text", "image", "audio", "video", "pdf"],
|
|
86
|
-
"output": ["text"]
|
|
87
|
-
},
|
|
88
|
-
"open_weights": false,
|
|
89
|
-
"cost": {
|
|
90
|
-
"input": 0.3,
|
|
91
|
-
"output": 2.5,
|
|
92
|
-
"cache_read": 0.075,
|
|
93
|
-
"input_audio": 1
|
|
94
|
-
},
|
|
95
|
-
"limit": {
|
|
96
|
-
"context": 1048576,
|
|
97
|
-
"output": 65536
|
|
98
|
-
}
|
|
99
|
-
},
|
|
100
|
-
"gemini-2.5-flash-lite": {
|
|
101
|
-
"id": "gemini-2.5-flash-lite",
|
|
102
|
-
"name": "Gemini 2.5 Flash Lite",
|
|
103
|
-
"family": "gemini-flash-lite",
|
|
104
|
-
"attachment": true,
|
|
105
|
-
"reasoning": true,
|
|
106
|
-
"tool_call": true,
|
|
107
|
-
"structured_output": true,
|
|
108
|
-
"temperature": true,
|
|
109
|
-
"knowledge": "2025-01",
|
|
110
|
-
"release_date": "2025-06-17",
|
|
111
|
-
"last_updated": "2025-06-17",
|
|
112
|
-
"modalities": {
|
|
113
|
-
"input": ["text", "image", "audio", "video", "pdf"],
|
|
114
|
-
"output": ["text"]
|
|
115
|
-
},
|
|
116
|
-
"open_weights": false,
|
|
117
|
-
"cost": {
|
|
118
|
-
"input": 0.1,
|
|
119
|
-
"output": 0.4,
|
|
120
|
-
"cache_read": 0.025
|
|
121
|
-
},
|
|
122
|
-
"limit": {
|
|
123
|
-
"context": 1048576,
|
|
124
|
-
"output": 65536
|
|
125
|
-
}
|
|
126
|
-
},
|
|
127
|
-
"gemini-2.5-pro": {
|
|
128
|
-
"id": "gemini-2.5-pro",
|
|
129
|
-
"name": "Gemini 2.5 Pro",
|
|
130
|
-
"family": "gemini-pro",
|
|
131
|
-
"attachment": true,
|
|
132
|
-
"reasoning": true,
|
|
133
|
-
"tool_call": true,
|
|
134
|
-
"structured_output": true,
|
|
135
|
-
"temperature": true,
|
|
136
|
-
"knowledge": "2025-01",
|
|
137
|
-
"release_date": "2025-03-20",
|
|
138
|
-
"last_updated": "2025-06-05",
|
|
139
|
-
"modalities": {
|
|
140
|
-
"input": ["text", "image", "audio", "video", "pdf"],
|
|
141
|
-
"output": ["text"]
|
|
142
|
-
},
|
|
143
|
-
"open_weights": false,
|
|
144
|
-
"cost": {
|
|
145
|
-
"input": 1.25,
|
|
146
|
-
"output": 10,
|
|
147
|
-
"cache_read": 0.31
|
|
148
|
-
},
|
|
149
|
-
"limit": {
|
|
150
|
-
"context": 1048576,
|
|
151
|
-
"output": 65536
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
|
-
}
|
|
@@ -1,194 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "bun:test"
|
|
2
|
-
import { transformRequest } from "../transform/request"
|
|
3
|
-
import { antigravityConfig, ProviderConfig } from "./config"
|
|
4
|
-
import { Session } from "./session"
|
|
5
|
-
import { Effect, Layer, pipe } from "effect"
|
|
6
|
-
import { generateText } from "ai"
|
|
7
|
-
import { createGoogleGenerativeAI } from "@ai-sdk/google"
|
|
8
|
-
|
|
9
|
-
describe("Antigravity transformRequest", () => {
|
|
10
|
-
const baseParams = {
|
|
11
|
-
accessToken: "test-token",
|
|
12
|
-
projectId: "test-project-123",
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
const config = antigravityConfig()
|
|
16
|
-
|
|
17
|
-
const MockSession = Layer.succeed(
|
|
18
|
-
Session,
|
|
19
|
-
Session.of({
|
|
20
|
-
getAccessToken: Effect.succeed(baseParams.accessToken),
|
|
21
|
-
ensureProject: Effect.succeed({
|
|
22
|
-
cloudaicompanionProject: baseParams.projectId,
|
|
23
|
-
currentTier: {
|
|
24
|
-
id: "free",
|
|
25
|
-
name: "Free",
|
|
26
|
-
description: "",
|
|
27
|
-
userDefinedCloudaicompanionProject: false,
|
|
28
|
-
userDefinedProjectId: "test-project-123",
|
|
29
|
-
},
|
|
30
|
-
allowedTiers: [],
|
|
31
|
-
gcpManaged: false,
|
|
32
|
-
manageSubscriptionUri: "",
|
|
33
|
-
}),
|
|
34
|
-
setCredentials: () => Effect.void,
|
|
35
|
-
} as unknown as Session),
|
|
36
|
-
).pipe(Layer.provideMerge(Layer.succeed(ProviderConfig, config)))
|
|
37
|
-
|
|
38
|
-
const setupTest = () => {
|
|
39
|
-
let capturedBody: unknown = null
|
|
40
|
-
|
|
41
|
-
const mockFetch = async (input: string | Request, init?: RequestInit) => {
|
|
42
|
-
const result = await pipe(
|
|
43
|
-
transformRequest(
|
|
44
|
-
input,
|
|
45
|
-
init as unknown as Parameters<typeof fetch>[1],
|
|
46
|
-
"https://example.com",
|
|
47
|
-
),
|
|
48
|
-
Effect.provide(MockSession),
|
|
49
|
-
Effect.runPromise,
|
|
50
|
-
)
|
|
51
|
-
capturedBody = JSON.parse(result.init.body as string)
|
|
52
|
-
|
|
53
|
-
return new Response(
|
|
54
|
-
JSON.stringify({
|
|
55
|
-
candidates: [
|
|
56
|
-
{
|
|
57
|
-
content: {
|
|
58
|
-
parts: [{ text: "Hello" }],
|
|
59
|
-
role: "model",
|
|
60
|
-
},
|
|
61
|
-
finishReason: "STOP",
|
|
62
|
-
},
|
|
63
|
-
],
|
|
64
|
-
usageMetadata: {
|
|
65
|
-
promptTokenCount: 1,
|
|
66
|
-
candidatesTokenCount: 1,
|
|
67
|
-
totalTokenCount: 2,
|
|
68
|
-
},
|
|
69
|
-
}),
|
|
70
|
-
{
|
|
71
|
-
status: 200,
|
|
72
|
-
headers: { "Content-Type": "application/json" },
|
|
73
|
-
},
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const google = createGoogleGenerativeAI({
|
|
78
|
-
apiKey: "test-key",
|
|
79
|
-
fetch: mockFetch as typeof fetch,
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
return {
|
|
83
|
-
google,
|
|
84
|
-
getCapturedBody: () => capturedBody as Record<string, unknown>,
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
it("propagates sessionId from labels and cleans up labels", async () => {
|
|
89
|
-
const { google, getCapturedBody } = setupTest()
|
|
90
|
-
|
|
91
|
-
await generateText({
|
|
92
|
-
model: google("gemini-1.5-flash"),
|
|
93
|
-
prompt: "Hello",
|
|
94
|
-
providerOptions: {
|
|
95
|
-
google: {
|
|
96
|
-
labels: {
|
|
97
|
-
sessionId: "test-session-id",
|
|
98
|
-
otherLabel: "keep-me",
|
|
99
|
-
},
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
})
|
|
103
|
-
|
|
104
|
-
const body = getCapturedBody()
|
|
105
|
-
const innerRequest = body.request as Record<string, unknown>
|
|
106
|
-
const labels = innerRequest.labels as Record<string, unknown>
|
|
107
|
-
|
|
108
|
-
// sessionId should be moved to innerRequest and transformed
|
|
109
|
-
// The actual format is `-${uuid}:${model}:${project}:seed-${hashedSession}`
|
|
110
|
-
expect(innerRequest.sessionId).not.toBe("test-session-id")
|
|
111
|
-
expect(innerRequest.sessionId).toBeString()
|
|
112
|
-
expect(
|
|
113
|
-
(innerRequest.sessionId as string).includes("gemini-1.5-flash"),
|
|
114
|
-
).toBe(true)
|
|
115
|
-
expect(
|
|
116
|
-
(innerRequest.sessionId as string).includes("test-project-123"),
|
|
117
|
-
).toBe(true)
|
|
118
|
-
|
|
119
|
-
// labels should be cleaned up (sessionId removed)
|
|
120
|
-
expect(labels.sessionId).toBeUndefined()
|
|
121
|
-
expect(labels.otherLabel).toBe("keep-me")
|
|
122
|
-
|
|
123
|
-
// Metadata should be present
|
|
124
|
-
expect(body.requestType).toBe("agent")
|
|
125
|
-
expect(body.userAgent).toBe("antigravity")
|
|
126
|
-
expect(body.requestId).toBeDefined()
|
|
127
|
-
})
|
|
128
|
-
|
|
129
|
-
it("removes labels object if it becomes empty after sessionId extraction", async () => {
|
|
130
|
-
const { google, getCapturedBody } = setupTest()
|
|
131
|
-
|
|
132
|
-
await generateText({
|
|
133
|
-
model: google("gemini-1.5-flash"),
|
|
134
|
-
prompt: "Hello",
|
|
135
|
-
providerOptions: {
|
|
136
|
-
google: {
|
|
137
|
-
labels: {
|
|
138
|
-
sessionId: "test-session-id",
|
|
139
|
-
},
|
|
140
|
-
},
|
|
141
|
-
},
|
|
142
|
-
})
|
|
143
|
-
|
|
144
|
-
const body = getCapturedBody()
|
|
145
|
-
const innerRequest = body.request as Record<string, unknown>
|
|
146
|
-
|
|
147
|
-
expect(innerRequest.sessionId).not.toBe("test-session-id")
|
|
148
|
-
expect(innerRequest.sessionId).toBeString()
|
|
149
|
-
expect(innerRequest.labels).toBeUndefined()
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
it("replaces todoread tool definition with placeholder schema", async () => {
|
|
153
|
-
const { google, getCapturedBody } = setupTest()
|
|
154
|
-
|
|
155
|
-
await generateText({
|
|
156
|
-
model: google("gemini-1.5-flash"),
|
|
157
|
-
prompt: "Hello",
|
|
158
|
-
tools: {
|
|
159
|
-
todoread: {
|
|
160
|
-
description: "Use this tool to read your todo list",
|
|
161
|
-
parameters: { type: "object", properties: {} },
|
|
162
|
-
},
|
|
163
|
-
},
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
const body = getCapturedBody()
|
|
167
|
-
const innerRequest = body.request as Record<string, unknown>
|
|
168
|
-
const tools = innerRequest.tools as Array<Record<string, unknown>>
|
|
169
|
-
const firstTool = tools[0]
|
|
170
|
-
expect(firstTool).toBeDefined()
|
|
171
|
-
if (!firstTool) throw new Error("firstTool is undefined")
|
|
172
|
-
const functionDeclarations = firstTool.functionDeclarations as Array<
|
|
173
|
-
Record<string, unknown>
|
|
174
|
-
>
|
|
175
|
-
const todoread = functionDeclarations.find((f) => f.name === "todoread")
|
|
176
|
-
|
|
177
|
-
expect(todoread).toBeDefined()
|
|
178
|
-
expect(todoread).toEqual({
|
|
179
|
-
name: "todoread",
|
|
180
|
-
description: "Use this tool to read your todo list",
|
|
181
|
-
parameters: {
|
|
182
|
-
type: "object",
|
|
183
|
-
properties: {
|
|
184
|
-
_placeholder: {
|
|
185
|
-
type: "boolean",
|
|
186
|
-
description: "Placeholder. Always pass true.",
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
required: ["_placeholder"],
|
|
190
|
-
additionalProperties: false,
|
|
191
|
-
},
|
|
192
|
-
})
|
|
193
|
-
})
|
|
194
|
-
})
|