opencode-google-auth 0.0.1 → 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 +12 -15
- package/src/antigravity-spoof.txt +1 -0
- package/src/lib/logger.ts +6 -3
- package/src/lib/runtime.ts +4 -4
- package/src/main.ts +8 -3
- package/src/{lib/services → services}/config.ts +30 -91
- package/src/{lib/services → services}/oauth.ts +12 -1
- package/src/{lib/services → services}/session.ts +1 -1
- package/src/transform/request.test.ts +2 -2
- package/src/transform/request.ts +4 -5
- package/dist/main.mjs +0 -716
- package/dist/main.mjs.map +0 -1
- package/src/lib/services/config.test.ts +0 -194
- package/src/models.json +0 -198
- /package/src/{lib/services → services}/opencode.ts +0 -0
package/dist/main.mjs
DELETED
|
@@ -1,716 +0,0 @@
|
|
|
1
|
-
import { FetchHttpClient, HttpClient, HttpClientRequest, HttpClientResponse, HttpRouter, HttpServer, HttpServerRequest, HttpServerResponse, PlatformLogger } from "@effect/platform";
|
|
2
|
-
import { Context, Data, Deferred, Effect, Fiber, Inspectable, Layer, LogLevel, Logger, ManagedRuntime, Ref, Schema, Stream, pipe } from "effect";
|
|
3
|
-
import { BunFileSystem, BunHttpServer } from "@effect/platform-bun";
|
|
4
|
-
import path from "node:path";
|
|
5
|
-
import { OAuth2Client } from "google-auth-library";
|
|
6
|
-
import { regex } from "arkregex";
|
|
7
|
-
import { Retry, encoder, makeChannel } from "@effect/experimental/Sse";
|
|
8
|
-
|
|
9
|
-
//#region src/lib/services/config.ts
|
|
10
|
-
var ProviderConfig = class extends Context.Tag("ProviderConfig")() {};
|
|
11
|
-
const CODE_ASSIST_VERSION = "v1internal";
|
|
12
|
-
const GEMINI_CLI_MODELS = [
|
|
13
|
-
"gemini-2.5-pro",
|
|
14
|
-
"gemini-2.5-flash",
|
|
15
|
-
"gemini-2.5-flash-lite",
|
|
16
|
-
"gemini-3-pro-preview",
|
|
17
|
-
"gemini-3-flash-preview"
|
|
18
|
-
];
|
|
19
|
-
const geminiCliConfig = () => ({
|
|
20
|
-
SERVICE_NAME: "gemini-cli",
|
|
21
|
-
DISPLAY_NAME: "Gemini CLI",
|
|
22
|
-
CLIENT_ID: "681255809395-oo8ft2oprdrnp9e3aqf6av3hmdib135j.apps.googleusercontent.com",
|
|
23
|
-
CLIENT_SECRET: "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",
|
|
24
|
-
SCOPES: [
|
|
25
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
26
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
27
|
-
"https://www.googleapis.com/auth/userinfo.profile"
|
|
28
|
-
],
|
|
29
|
-
ENDPOINTS: ["https://cloudcode-pa.googleapis.com"],
|
|
30
|
-
HEADERS: {
|
|
31
|
-
"User-Agent": "google-api-nodejs-client/9.15.1",
|
|
32
|
-
"X-Goog-Api-Client": "gl-node/22.17.0",
|
|
33
|
-
"Client-Metadata": "ideType=IDE_UNSPECIFIED,platform=PLATFORM_UNSPECIFIED,pluginType=GEMINI"
|
|
34
|
-
},
|
|
35
|
-
getConfig: (modelsDev) => {
|
|
36
|
-
const provider = modelsDev.google;
|
|
37
|
-
const filteredModels = pipe(provider.models, (models) => Object.entries(models), (entries) => entries.filter(([key]) => GEMINI_CLI_MODELS.includes(key)), (filtered) => Object.fromEntries(filtered));
|
|
38
|
-
return {
|
|
39
|
-
...provider,
|
|
40
|
-
id: geminiCliConfig().SERVICE_NAME,
|
|
41
|
-
name: geminiCliConfig().DISPLAY_NAME,
|
|
42
|
-
api: geminiCliConfig().ENDPOINTS.at(0),
|
|
43
|
-
models: filteredModels
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
});
|
|
47
|
-
const antigravityConfig = () => ({
|
|
48
|
-
SERVICE_NAME: "antigravity",
|
|
49
|
-
DISPLAY_NAME: "Antigravity",
|
|
50
|
-
CLIENT_ID: "1071006060591-tmhssin2h21lcre235vtolojh4g403ep.apps.googleusercontent.com",
|
|
51
|
-
CLIENT_SECRET: "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",
|
|
52
|
-
SCOPES: [
|
|
53
|
-
"https://www.googleapis.com/auth/cloud-platform",
|
|
54
|
-
"https://www.googleapis.com/auth/userinfo.email",
|
|
55
|
-
"https://www.googleapis.com/auth/userinfo.profile",
|
|
56
|
-
"https://www.googleapis.com/auth/cclog",
|
|
57
|
-
"https://www.googleapis.com/auth/experimentsandconfigs"
|
|
58
|
-
],
|
|
59
|
-
ENDPOINTS: [
|
|
60
|
-
"https://daily-cloudcode-pa.sandbox.googleapis.com",
|
|
61
|
-
"https://autopush-cloudcode-pa.sandbox.googleapis.com",
|
|
62
|
-
"https://cloudcode-pa.googleapis.com"
|
|
63
|
-
],
|
|
64
|
-
HEADERS: {
|
|
65
|
-
"User-Agent": "antigravity/1.11.5 windows/amd64",
|
|
66
|
-
"X-Goog-Api-Client": "google-cloud-sdk vscode_cloudshelleditor/0.1",
|
|
67
|
-
"Client-Metadata": "{\"ideType\":\"IDE_UNSPECIFIED\",\"platform\":\"PLATFORM_UNSPECIFIED\",\"pluginType\":\"GEMINI\"}"
|
|
68
|
-
},
|
|
69
|
-
getConfig: (modelsDev) => {
|
|
70
|
-
const googleProvider = modelsDev.google;
|
|
71
|
-
const googleVertextProvider = modelsDev["google-vertex-anthropic"];
|
|
72
|
-
const geminiFlash = googleProvider.models["gemini-3-flash-preview"];
|
|
73
|
-
const geminiPro = googleProvider.models["gemini-3-pro-preview"];
|
|
74
|
-
const claudeSonnet = googleVertextProvider.models["claude-sonnet-4-5@20250929"];
|
|
75
|
-
const claudeOpus = googleVertextProvider.models["claude-opus-4-5@20251101"];
|
|
76
|
-
const models = {
|
|
77
|
-
"gemini-3-flash": {
|
|
78
|
-
...geminiFlash,
|
|
79
|
-
id: "gemini-3-flash"
|
|
80
|
-
},
|
|
81
|
-
"gemini-3-pro-low": {
|
|
82
|
-
...geminiPro,
|
|
83
|
-
id: "gemini-3-pro-low",
|
|
84
|
-
name: "Gemini 3 Pro (Low)",
|
|
85
|
-
temperature: false,
|
|
86
|
-
options: { thinkingConfig: { thinkingLevel: "low" } }
|
|
87
|
-
},
|
|
88
|
-
"gemini-3-pro-high": {
|
|
89
|
-
...geminiPro,
|
|
90
|
-
id: "gemini-3-pro-high",
|
|
91
|
-
name: "Gemini 3 Pro (High)",
|
|
92
|
-
temperature: false,
|
|
93
|
-
options: { thinkingConfig: { thinkingLevel: "high" } }
|
|
94
|
-
},
|
|
95
|
-
"claude-sonnet-4-5": {
|
|
96
|
-
...claudeSonnet,
|
|
97
|
-
id: "claude-sonnet-4-5",
|
|
98
|
-
reasoning: false,
|
|
99
|
-
options: { thinkingConfig: { includeThoughts: false } }
|
|
100
|
-
},
|
|
101
|
-
"claude-sonnet-4-5-thinking": {
|
|
102
|
-
...claudeSonnet,
|
|
103
|
-
id: "claude-sonnet-4-5-thinking",
|
|
104
|
-
name: "Claude Sonnet 4.5 (Reasoning)"
|
|
105
|
-
},
|
|
106
|
-
"claude-opus-4-5-thinking": {
|
|
107
|
-
...claudeOpus,
|
|
108
|
-
id: "claude-opus-4-5-thinking",
|
|
109
|
-
name: "Claude Opus 4.5 (Reasoning)"
|
|
110
|
-
}
|
|
111
|
-
};
|
|
112
|
-
return {
|
|
113
|
-
...googleProvider,
|
|
114
|
-
id: antigravityConfig().SERVICE_NAME,
|
|
115
|
-
name: antigravityConfig().DISPLAY_NAME,
|
|
116
|
-
api: antigravityConfig().ENDPOINTS.at(2),
|
|
117
|
-
models
|
|
118
|
-
};
|
|
119
|
-
},
|
|
120
|
-
transformRequest: Effect.fn(function* (context) {
|
|
121
|
-
yield* Effect.log("Transforming request for: ", antigravityConfig().SERVICE_NAME);
|
|
122
|
-
const { body, headers, url } = context;
|
|
123
|
-
const innerRequest = body.request;
|
|
124
|
-
let sessionId;
|
|
125
|
-
if (innerRequest.labels && typeof innerRequest.labels === "object" && "sessionId" in innerRequest.labels) {
|
|
126
|
-
const labels = innerRequest.labels;
|
|
127
|
-
sessionId = labels.sessionId;
|
|
128
|
-
delete labels.sessionId;
|
|
129
|
-
if (Object.keys(labels).length === 0) delete innerRequest.labels;
|
|
130
|
-
}
|
|
131
|
-
const isClaude = body.model.toLowerCase().includes("claude");
|
|
132
|
-
const isThinking = body.model.toLowerCase().includes("thinking");
|
|
133
|
-
if (isClaude && body.request && typeof body.request === "object") {
|
|
134
|
-
const generationConfig = body.request.generationConfig;
|
|
135
|
-
innerRequest.toolConfig = { functionCallingConfig: { mode: "VALIDATED" } };
|
|
136
|
-
if (!isThinking && generationConfig?.thinkingConfig) delete generationConfig.thinkingConfig;
|
|
137
|
-
if (isThinking && generationConfig?.thinkingConfig) {
|
|
138
|
-
const thinkingConfig = generationConfig.thinkingConfig;
|
|
139
|
-
if (thinkingConfig.includeThoughts !== void 0) {
|
|
140
|
-
thinkingConfig.include_thoughts = thinkingConfig.includeThoughts;
|
|
141
|
-
delete thinkingConfig.includeThoughts;
|
|
142
|
-
}
|
|
143
|
-
if (thinkingConfig.thinkingBudget !== void 0) {
|
|
144
|
-
thinkingConfig.thinking_budget = thinkingConfig.thinkingBudget;
|
|
145
|
-
delete thinkingConfig.thinkingBudget;
|
|
146
|
-
}
|
|
147
|
-
if (thinkingConfig.thinking_budget === void 0) thinkingConfig.thinking_budget = 32768;
|
|
148
|
-
}
|
|
149
|
-
if (isThinking) headers.set("anthropic-beta", "interleaved-thinking-2025-05-14");
|
|
150
|
-
}
|
|
151
|
-
if (sessionId) {
|
|
152
|
-
const hashedSession = yield* Effect.promise(() => hash(sessionId));
|
|
153
|
-
innerRequest.sessionId = [
|
|
154
|
-
`-${crypto.randomUUID()}`,
|
|
155
|
-
body.model,
|
|
156
|
-
body.project,
|
|
157
|
-
`seed-${hashedSession}`
|
|
158
|
-
].join(":");
|
|
159
|
-
}
|
|
160
|
-
if (innerRequest.tools && Array.isArray(innerRequest.tools)) {
|
|
161
|
-
const tools = innerRequest.tools;
|
|
162
|
-
for (const tool of tools) if (tool.functionDeclarations && Array.isArray(tool.functionDeclarations)) {
|
|
163
|
-
const functionDeclarations = tool.functionDeclarations;
|
|
164
|
-
for (let i = 0; i < functionDeclarations.length; i++) {
|
|
165
|
-
const declaration = functionDeclarations[i];
|
|
166
|
-
if (declaration && declaration.name === "todoread") functionDeclarations[i] = {
|
|
167
|
-
...functionDeclarations[i],
|
|
168
|
-
parameters: {
|
|
169
|
-
type: "object",
|
|
170
|
-
properties: { _placeholder: {
|
|
171
|
-
type: "boolean",
|
|
172
|
-
description: "Placeholder. Always pass true."
|
|
173
|
-
} },
|
|
174
|
-
required: ["_placeholder"],
|
|
175
|
-
additionalProperties: false
|
|
176
|
-
}
|
|
177
|
-
};
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
if (innerRequest.systemInstruction && typeof innerRequest.systemInstruction === "object") {
|
|
182
|
-
const systemInstruction = innerRequest.systemInstruction;
|
|
183
|
-
systemInstruction.role = "user";
|
|
184
|
-
}
|
|
185
|
-
return {
|
|
186
|
-
headers,
|
|
187
|
-
url,
|
|
188
|
-
body: {
|
|
189
|
-
...body,
|
|
190
|
-
requestType: "agent",
|
|
191
|
-
userAgent: "antigravity",
|
|
192
|
-
requestId: `agent-${crypto.randomUUID()}`
|
|
193
|
-
}
|
|
194
|
-
};
|
|
195
|
-
})
|
|
196
|
-
});
|
|
197
|
-
async function hash(str) {
|
|
198
|
-
const data = new TextEncoder().encode(str);
|
|
199
|
-
const hashBuffer = await crypto.subtle.digest("SHA-256", data);
|
|
200
|
-
return Array.from(new Uint8Array(hashBuffer)).map((b) => b.toString(16).padStart(2, "0")).join("").slice(0, 16);
|
|
201
|
-
}
|
|
202
|
-
|
|
203
|
-
//#endregion
|
|
204
|
-
//#region src/lib/services/opencode.ts
|
|
205
|
-
var OpenCodeContext = class extends Context.Tag("OpenCodeContext")() {};
|
|
206
|
-
|
|
207
|
-
//#endregion
|
|
208
|
-
//#region src/lib/logger.ts
|
|
209
|
-
const makeOpenCodeLogger = Effect.gen(function* () {
|
|
210
|
-
const openCode = yield* OpenCodeContext;
|
|
211
|
-
const config = yield* ProviderConfig;
|
|
212
|
-
return Logger.make((log) => {
|
|
213
|
-
let level = "debug";
|
|
214
|
-
if (LogLevel.greaterThanEqual(log.logLevel, LogLevel.Error)) level = "error";
|
|
215
|
-
else if (LogLevel.greaterThanEqual(log.logLevel, LogLevel.Warning)) level = "warn";
|
|
216
|
-
else if (LogLevel.greaterThanEqual(log.logLevel, LogLevel.Info)) level = "info";
|
|
217
|
-
const message = Inspectable.toStringUnknown(log.message);
|
|
218
|
-
openCode.client.app.log({ body: {
|
|
219
|
-
level,
|
|
220
|
-
message,
|
|
221
|
-
service: config.SERVICE_NAME
|
|
222
|
-
} });
|
|
223
|
-
});
|
|
224
|
-
});
|
|
225
|
-
const combinedLogger = Effect.gen(function* () {
|
|
226
|
-
const openCodeLogger = yield* makeOpenCodeLogger;
|
|
227
|
-
const fileLogger = yield* pipe(Logger.jsonLogger, PlatformLogger.toFile(path.join(import.meta.dir, "plugin.log")));
|
|
228
|
-
return Logger.zip(openCodeLogger, fileLogger);
|
|
229
|
-
});
|
|
230
|
-
|
|
231
|
-
//#endregion
|
|
232
|
-
//#region src/lib/services/oauth.ts
|
|
233
|
-
/**
|
|
234
|
-
* OAuth service
|
|
235
|
-
*
|
|
236
|
-
* Handles initial OAuth authentication flow only.
|
|
237
|
-
* Token refresh is handled by the Session service.
|
|
238
|
-
*/
|
|
239
|
-
var OAuthError = class extends Data.TaggedError("OAuthError") {};
|
|
240
|
-
const SuccessParamsSchema = Schema.Struct({
|
|
241
|
-
code: Schema.String,
|
|
242
|
-
state: Schema.String
|
|
243
|
-
});
|
|
244
|
-
const FailureParamsSchema = Schema.Struct({
|
|
245
|
-
error: Schema.String,
|
|
246
|
-
error_description: Schema.optional(Schema.String),
|
|
247
|
-
state: Schema.optional(Schema.String)
|
|
248
|
-
});
|
|
249
|
-
const isFailureParams = Schema.is(FailureParamsSchema);
|
|
250
|
-
const ParamsSchema = Schema.Union(SuccessParamsSchema, FailureParamsSchema);
|
|
251
|
-
var OAuth = class extends Effect.Service()("OAuth", { effect: Effect.gen(function* () {
|
|
252
|
-
const config = yield* ProviderConfig;
|
|
253
|
-
const client = new OAuth2Client({
|
|
254
|
-
clientId: config.CLIENT_ID,
|
|
255
|
-
clientSecret: config.CLIENT_SECRET
|
|
256
|
-
});
|
|
257
|
-
const ServerLive = BunHttpServer.layerServer({ port: 0 });
|
|
258
|
-
return { authenticate: Effect.gen(function* () {
|
|
259
|
-
yield* HttpServer.logAddress;
|
|
260
|
-
const deferredParams = yield* Deferred.make();
|
|
261
|
-
const redirectUri = yield* HttpServer.addressFormattedWith((address) => Effect.succeed(`${address}/oauth2callback`));
|
|
262
|
-
const state = crypto.randomUUID();
|
|
263
|
-
const authUrl = client.generateAuthUrl({
|
|
264
|
-
state,
|
|
265
|
-
redirect_uri: redirectUri,
|
|
266
|
-
access_type: "offline",
|
|
267
|
-
scope: config.SCOPES,
|
|
268
|
-
prompt: "consent"
|
|
269
|
-
});
|
|
270
|
-
yield* Effect.log(`OAuth2 authorization URL: ${authUrl}`);
|
|
271
|
-
const serverFiber = yield* HttpRouter.empty.pipe(HttpRouter.get("/oauth2callback", Effect.gen(function* () {
|
|
272
|
-
const params = yield* HttpServerRequest.schemaSearchParams(ParamsSchema);
|
|
273
|
-
if (isFailureParams(params)) yield* Deferred.fail(deferredParams, new OAuthError({
|
|
274
|
-
reason: "callback",
|
|
275
|
-
message: `${params.error} - ${params.error_description ?? "No additional details provided"}`
|
|
276
|
-
}));
|
|
277
|
-
else yield* Deferred.succeed(deferredParams, params);
|
|
278
|
-
return yield* HttpServerResponse.text("You may now close this tab.");
|
|
279
|
-
}).pipe(Effect.tapError(Effect.logError))), HttpServer.serveEffect(), Effect.fork);
|
|
280
|
-
yield* Effect.log("Started OAuth2 callback server");
|
|
281
|
-
const search = yield* Deferred.await(deferredParams);
|
|
282
|
-
yield* Effect.log("Received OAuth2 callback with params", search);
|
|
283
|
-
yield* Fiber.interrupt(serverFiber);
|
|
284
|
-
if (state !== search.state) return yield* new OAuthError({
|
|
285
|
-
reason: "state_mismatch",
|
|
286
|
-
message: "Invalid state parameter. Possible CSRF attack."
|
|
287
|
-
});
|
|
288
|
-
return (yield* Effect.tryPromise({
|
|
289
|
-
try: () => client.getToken({
|
|
290
|
-
code: search.code,
|
|
291
|
-
redirect_uri: redirectUri
|
|
292
|
-
}),
|
|
293
|
-
catch: (cause) => new OAuthError({
|
|
294
|
-
reason: "token_exchange",
|
|
295
|
-
message: "Failed to exchange authorization code for tokens",
|
|
296
|
-
cause
|
|
297
|
-
})
|
|
298
|
-
})).tokens;
|
|
299
|
-
}).pipe(Effect.provide(ServerLive), Effect.scoped) };
|
|
300
|
-
}) }) {};
|
|
301
|
-
|
|
302
|
-
//#endregion
|
|
303
|
-
//#region src/lib/services/session.ts
|
|
304
|
-
var SessionError = class extends Data.TaggedError("SessionError") {};
|
|
305
|
-
var TokenExpiredError = class extends Data.TaggedError("TokenExpiredError") {};
|
|
306
|
-
const CodeAssistTier = Schema.Struct({
|
|
307
|
-
id: Schema.String,
|
|
308
|
-
name: Schema.String,
|
|
309
|
-
description: Schema.String,
|
|
310
|
-
userDefinedCloudaicompanionProject: Schema.Boolean,
|
|
311
|
-
isDefault: Schema.optional(Schema.Boolean)
|
|
312
|
-
});
|
|
313
|
-
const LoadCodeAssistResponse = Schema.Struct({
|
|
314
|
-
currentTier: CodeAssistTier,
|
|
315
|
-
allowedTiers: Schema.Array(CodeAssistTier),
|
|
316
|
-
cloudaicompanionProject: Schema.String,
|
|
317
|
-
gcpManaged: Schema.Boolean,
|
|
318
|
-
manageSubscriptionUri: Schema.String
|
|
319
|
-
});
|
|
320
|
-
var Session = class extends Effect.Service()("Session", { effect: Effect.gen(function* () {
|
|
321
|
-
const config = yield* ProviderConfig;
|
|
322
|
-
const openCode = yield* OpenCodeContext;
|
|
323
|
-
const httpClient = yield* HttpClient.HttpClient;
|
|
324
|
-
const credentialsRef = yield* Ref.make(null);
|
|
325
|
-
const projectRef = yield* Ref.make(null);
|
|
326
|
-
const endpoint = config.ENDPOINTS.at(0);
|
|
327
|
-
const oauthClient = new OAuth2Client({
|
|
328
|
-
clientId: config.CLIENT_ID,
|
|
329
|
-
clientSecret: config.CLIENT_SECRET
|
|
330
|
-
});
|
|
331
|
-
const getCredentials = Effect.gen(function* () {
|
|
332
|
-
const current = yield* Ref.get(credentialsRef);
|
|
333
|
-
if (!current) return yield* new SessionError({
|
|
334
|
-
reason: "no_tokens",
|
|
335
|
-
message: "No credentials set"
|
|
336
|
-
});
|
|
337
|
-
return current;
|
|
338
|
-
});
|
|
339
|
-
const refreshTokens = Effect.gen(function* () {
|
|
340
|
-
const credentials = yield* getCredentials;
|
|
341
|
-
oauthClient.setCredentials(credentials);
|
|
342
|
-
const newCredentials = (yield* Effect.tryPromise({
|
|
343
|
-
try: () => oauthClient.refreshAccessToken(),
|
|
344
|
-
catch: (cause) => new SessionError({
|
|
345
|
-
reason: "token_refresh",
|
|
346
|
-
message: "Failed to refresh access token",
|
|
347
|
-
cause
|
|
348
|
-
})
|
|
349
|
-
})).credentials;
|
|
350
|
-
yield* Ref.set(credentialsRef, newCredentials);
|
|
351
|
-
const accessToken = newCredentials.access_token;
|
|
352
|
-
const refreshToken = newCredentials.refresh_token;
|
|
353
|
-
const expiryDate = newCredentials.expiry_date;
|
|
354
|
-
if (accessToken && refreshToken && expiryDate) yield* Effect.promise(() => openCode.client.auth.set({
|
|
355
|
-
path: { id: config.SERVICE_NAME },
|
|
356
|
-
body: {
|
|
357
|
-
type: "oauth",
|
|
358
|
-
access: accessToken,
|
|
359
|
-
refresh: refreshToken,
|
|
360
|
-
expires: expiryDate
|
|
361
|
-
}
|
|
362
|
-
}));
|
|
363
|
-
return newCredentials;
|
|
364
|
-
});
|
|
365
|
-
const fetchAttempt = Effect.gen(function* () {
|
|
366
|
-
const credentials = yield* getCredentials;
|
|
367
|
-
const request = yield* HttpClientRequest.post(`${endpoint}/${CODE_ASSIST_VERSION}:loadCodeAssist`).pipe(HttpClientRequest.bearerToken(credentials.access_token), HttpClientRequest.bodyJson({ metadata: {
|
|
368
|
-
ideType: "IDE_UNSPECIFIED",
|
|
369
|
-
platform: "PLATFORM_UNSPECIFIED",
|
|
370
|
-
pluginType: "GEMINI"
|
|
371
|
-
} }));
|
|
372
|
-
return yield* pipe(httpClient.execute(request), Effect.andThen(HttpClientResponse.matchStatus({
|
|
373
|
-
"2xx": (res) => HttpClientResponse.schemaBodyJson(LoadCodeAssistResponse)(res),
|
|
374
|
-
401: () => new TokenExpiredError({ message: "Token expired" }),
|
|
375
|
-
orElse: (response) => new SessionError({
|
|
376
|
-
reason: "project_fetch",
|
|
377
|
-
message: `HTTP error: ${response.status}`
|
|
378
|
-
})
|
|
379
|
-
})));
|
|
380
|
-
});
|
|
381
|
-
const fetchProject = fetchAttempt.pipe(Effect.catchTag("TokenExpiredError", () => pipe(Effect.log("Token expired, refreshing..."), Effect.flatMap(() => refreshTokens), Effect.flatMap(() => fetchAttempt))), Effect.catchAll((error) => {
|
|
382
|
-
if (error instanceof SessionError) return Effect.fail(error);
|
|
383
|
-
return Effect.fail(new SessionError({
|
|
384
|
-
reason: "project_fetch",
|
|
385
|
-
message: "Failed to fetch project",
|
|
386
|
-
cause: error
|
|
387
|
-
}));
|
|
388
|
-
}));
|
|
389
|
-
const ensureProject = Effect.gen(function* () {
|
|
390
|
-
const cached = yield* Ref.get(projectRef);
|
|
391
|
-
if (cached !== null) return cached;
|
|
392
|
-
const project = yield* fetchProject;
|
|
393
|
-
yield* Ref.set(projectRef, project);
|
|
394
|
-
return project;
|
|
395
|
-
});
|
|
396
|
-
return {
|
|
397
|
-
setCredentials: (credentials) => Ref.set(credentialsRef, credentials),
|
|
398
|
-
getAccessToken: Effect.gen(function* () {
|
|
399
|
-
const currentCreds = yield* Ref.get(credentialsRef);
|
|
400
|
-
if (!currentCreds?.access_token) return yield* new SessionError({
|
|
401
|
-
reason: "no_tokens",
|
|
402
|
-
message: "No access token available"
|
|
403
|
-
});
|
|
404
|
-
const isExpired = (currentCreds.expiry_date ?? 0) < Date.now() + 300 * 1e3;
|
|
405
|
-
let accessToken = currentCreds.access_token;
|
|
406
|
-
if (isExpired) {
|
|
407
|
-
yield* Effect.log("Access token expired, refreshing...");
|
|
408
|
-
const refreshed = yield* refreshTokens;
|
|
409
|
-
if (!refreshed.access_token) return yield* new SessionError({
|
|
410
|
-
reason: "token_refresh",
|
|
411
|
-
message: "Refresh did not return access token"
|
|
412
|
-
});
|
|
413
|
-
accessToken = refreshed.access_token;
|
|
414
|
-
}
|
|
415
|
-
yield* ensureProject;
|
|
416
|
-
return accessToken;
|
|
417
|
-
}),
|
|
418
|
-
ensureProject
|
|
419
|
-
};
|
|
420
|
-
}) }) {};
|
|
421
|
-
|
|
422
|
-
//#endregion
|
|
423
|
-
//#region src/lib/runtime.ts
|
|
424
|
-
const makeRuntime = ({ providerConfig, openCodeCtx }) => {
|
|
425
|
-
const LoggerLive = Logger.replaceScoped(Logger.defaultLogger, combinedLogger);
|
|
426
|
-
const ProviderConfigLive = Layer.succeed(ProviderConfig, providerConfig);
|
|
427
|
-
const OpenCodeLive = Layer.succeed(OpenCodeContext, openCodeCtx);
|
|
428
|
-
const MainLive = pipe(Layer.empty, Layer.provide(LoggerLive), Layer.provide(BunFileSystem.layer), Layer.merge(OAuth.Default), Layer.merge(Session.Default), Layer.provideMerge(OpenCodeLive), Layer.provideMerge(FetchHttpClient.layer), Layer.provideMerge(ProviderConfigLive));
|
|
429
|
-
return ManagedRuntime.make(MainLive);
|
|
430
|
-
};
|
|
431
|
-
|
|
432
|
-
//#endregion
|
|
433
|
-
//#region src/transform/request.ts
|
|
434
|
-
const STREAM_ACTION = "streamGenerateContent";
|
|
435
|
-
const PATH_PATTERN = regex("/models/(?<model>[^:]+):(?<action>\\w+)");
|
|
436
|
-
const transformRequest = Effect.fn("transformRequest")(function* (input, init, endpoint) {
|
|
437
|
-
const config = yield* ProviderConfig;
|
|
438
|
-
const session = yield* Session;
|
|
439
|
-
const accessToken = yield* session.getAccessToken;
|
|
440
|
-
const projectId = (yield* session.ensureProject).cloudaicompanionProject;
|
|
441
|
-
const url = new URL(input instanceof Request ? input.url : input);
|
|
442
|
-
const endpointUrl = new URL(endpoint);
|
|
443
|
-
url.protocol = endpointUrl.protocol;
|
|
444
|
-
url.host = endpointUrl.host;
|
|
445
|
-
const match = PATH_PATTERN.exec(url.pathname);
|
|
446
|
-
if (!match) return {
|
|
447
|
-
input: url.toString(),
|
|
448
|
-
init: init ?? {},
|
|
449
|
-
streaming: false
|
|
450
|
-
};
|
|
451
|
-
const { model, action } = match.groups;
|
|
452
|
-
const streaming = action === STREAM_ACTION;
|
|
453
|
-
url.pathname = `/${CODE_ASSIST_VERSION}:${action}`;
|
|
454
|
-
if (streaming) url.searchParams.set("alt", "sse");
|
|
455
|
-
const headers = new Headers(init?.headers);
|
|
456
|
-
headers.delete("x-api-key");
|
|
457
|
-
headers.set("x-opencode-tools-debug", "1");
|
|
458
|
-
headers.set("Authorization", `Bearer ${accessToken}`);
|
|
459
|
-
for (const [key, value] of Object.entries(config.HEADERS)) headers.set(key, value);
|
|
460
|
-
if (streaming) headers.set("Accept", "text/event-stream");
|
|
461
|
-
const isJson = typeof init?.body === "string";
|
|
462
|
-
const parsedBody = yield* pipe(Effect.try(() => isJson ? JSON.parse(init.body) : null), Effect.orElseSucceed(() => null));
|
|
463
|
-
const wrappedBody = {
|
|
464
|
-
project: projectId,
|
|
465
|
-
model,
|
|
466
|
-
request: parsedBody ?? {}
|
|
467
|
-
};
|
|
468
|
-
const { body: transformedBody, headers: finalHeaders, url: finalUrl } = config.transformRequest ? yield* config.transformRequest({
|
|
469
|
-
body: wrappedBody,
|
|
470
|
-
headers,
|
|
471
|
-
url
|
|
472
|
-
}) : {
|
|
473
|
-
body: wrappedBody,
|
|
474
|
-
headers,
|
|
475
|
-
url
|
|
476
|
-
};
|
|
477
|
-
const finalBody = isJson && parsedBody ? JSON.stringify(transformedBody) : init?.body;
|
|
478
|
-
return {
|
|
479
|
-
input: finalUrl.toString(),
|
|
480
|
-
init: {
|
|
481
|
-
...init,
|
|
482
|
-
headers: finalHeaders,
|
|
483
|
-
body: finalBody
|
|
484
|
-
},
|
|
485
|
-
streaming
|
|
486
|
-
};
|
|
487
|
-
});
|
|
488
|
-
|
|
489
|
-
//#endregion
|
|
490
|
-
//#region src/transform/response.ts
|
|
491
|
-
const transformNonStreamingResponse = async (response) => {
|
|
492
|
-
if (!response.headers.get("content-type")?.includes("application/json")) return response;
|
|
493
|
-
try {
|
|
494
|
-
const parsed = await response.clone().json();
|
|
495
|
-
if (parsed.response !== void 0) {
|
|
496
|
-
const { response: responseData, ...rest } = parsed;
|
|
497
|
-
return new Response(JSON.stringify({
|
|
498
|
-
...rest,
|
|
499
|
-
...responseData
|
|
500
|
-
}), {
|
|
501
|
-
status: response.status,
|
|
502
|
-
statusText: response.statusText,
|
|
503
|
-
headers: response.headers
|
|
504
|
-
});
|
|
505
|
-
}
|
|
506
|
-
} catch {}
|
|
507
|
-
return response;
|
|
508
|
-
};
|
|
509
|
-
|
|
510
|
-
//#endregion
|
|
511
|
-
//#region src/transform/stream.ts
|
|
512
|
-
const parseAndMerge = (event) => {
|
|
513
|
-
if (!event.data) return encoder.write(event);
|
|
514
|
-
try {
|
|
515
|
-
const parsed = JSON.parse(event.data);
|
|
516
|
-
if (parsed.response) {
|
|
517
|
-
const { response, ...rest } = parsed;
|
|
518
|
-
return encoder.write({
|
|
519
|
-
...event,
|
|
520
|
-
data: JSON.stringify({
|
|
521
|
-
...rest,
|
|
522
|
-
...response
|
|
523
|
-
})
|
|
524
|
-
});
|
|
525
|
-
}
|
|
526
|
-
return encoder.write(event);
|
|
527
|
-
} catch {
|
|
528
|
-
return encoder.write(event);
|
|
529
|
-
}
|
|
530
|
-
};
|
|
531
|
-
const parseSSE = (body) => pipe(Stream.fromReadableStream(() => body, (error) => error), Stream.decodeText, Stream.pipeThroughChannel(makeChannel()), Stream.map((event) => Retry.is(event) ? encoder.write(event) : parseAndMerge(event)), Stream.encodeText);
|
|
532
|
-
const transformStreamingResponse = (response) => {
|
|
533
|
-
if (!response.body) return response;
|
|
534
|
-
const transformed = parseSSE(response.body);
|
|
535
|
-
const readable = Stream.toReadableStream(transformed);
|
|
536
|
-
return new Response(readable, {
|
|
537
|
-
status: response.status,
|
|
538
|
-
statusText: response.statusText,
|
|
539
|
-
headers: response.headers
|
|
540
|
-
});
|
|
541
|
-
};
|
|
542
|
-
|
|
543
|
-
//#endregion
|
|
544
|
-
//#region src/main.ts
|
|
545
|
-
const fetchModelsDev = Effect.gen(function* () {
|
|
546
|
-
return yield* (yield* (yield* HttpClient.HttpClient).get("https://models.dev/api.json")).json;
|
|
547
|
-
});
|
|
548
|
-
const customFetch = Effect.fn(function* (input, init) {
|
|
549
|
-
const config = yield* ProviderConfig;
|
|
550
|
-
let lastResponse = null;
|
|
551
|
-
for (const endpoint of config.ENDPOINTS) {
|
|
552
|
-
const result = yield* transformRequest(input, init, endpoint);
|
|
553
|
-
const { request, ...loggedBody } = JSON.parse(result.init.body);
|
|
554
|
-
const generationConfig = request.generationConfig;
|
|
555
|
-
yield* Effect.log("Transformed request (Omitting request except generationConfig) :", result.streaming, result.input, {
|
|
556
|
-
...loggedBody,
|
|
557
|
-
request: { generationConfig }
|
|
558
|
-
});
|
|
559
|
-
const response = yield* Effect.promise(() => fetch(result.input, result.init));
|
|
560
|
-
if (response.status === 429 || response.status === 403) {
|
|
561
|
-
yield* Effect.log(`${response.status} on ${endpoint}, trying next...`);
|
|
562
|
-
lastResponse = response;
|
|
563
|
-
continue;
|
|
564
|
-
}
|
|
565
|
-
if (!response.ok) {
|
|
566
|
-
const cloned = response.clone();
|
|
567
|
-
const clonedJson = yield* Effect.promise(() => cloned.json());
|
|
568
|
-
yield* Effect.log("Received response:", cloned.status, clonedJson, cloned.headers);
|
|
569
|
-
}
|
|
570
|
-
return result.streaming ? transformStreamingResponse(response) : yield* Effect.promise(() => transformNonStreamingResponse(response));
|
|
571
|
-
}
|
|
572
|
-
yield* Effect.logWarning("All endpoints rate limited (429)");
|
|
573
|
-
return lastResponse;
|
|
574
|
-
}, Effect.tapDefect(Effect.logError));
|
|
575
|
-
const geminiCli = async (context) => {
|
|
576
|
-
const runtime = makeRuntime({
|
|
577
|
-
openCodeCtx: context,
|
|
578
|
-
providerConfig: geminiCliConfig()
|
|
579
|
-
});
|
|
580
|
-
const config = await runtime.runPromise(Effect.gen(function* () {
|
|
581
|
-
const providerConfig = yield* ProviderConfig;
|
|
582
|
-
const modelsDev = yield* fetchModelsDev;
|
|
583
|
-
return providerConfig.getConfig(modelsDev);
|
|
584
|
-
}));
|
|
585
|
-
return {
|
|
586
|
-
config: async (cfg) => {
|
|
587
|
-
cfg.provider ??= {};
|
|
588
|
-
cfg.provider[config.id] = config;
|
|
589
|
-
},
|
|
590
|
-
auth: {
|
|
591
|
-
provider: config.id,
|
|
592
|
-
loader: async (getAuth) => {
|
|
593
|
-
const auth = await getAuth();
|
|
594
|
-
if (auth.type !== "oauth") return {};
|
|
595
|
-
const credentials = {
|
|
596
|
-
access_token: auth.access,
|
|
597
|
-
refresh_token: auth.refresh,
|
|
598
|
-
expiry_date: auth.expires
|
|
599
|
-
};
|
|
600
|
-
await runtime.runPromise(Effect.gen(function* () {
|
|
601
|
-
yield* (yield* Session).setCredentials(credentials);
|
|
602
|
-
}));
|
|
603
|
-
return {
|
|
604
|
-
apiKey: "",
|
|
605
|
-
fetch: (async (input, init) => {
|
|
606
|
-
return await runtime.runPromise(customFetch(input, init));
|
|
607
|
-
})
|
|
608
|
-
};
|
|
609
|
-
},
|
|
610
|
-
methods: [{
|
|
611
|
-
type: "oauth",
|
|
612
|
-
label: "OAuth with Google",
|
|
613
|
-
authorize: async () => {
|
|
614
|
-
const result = await runtime.runPromise(Effect.gen(function* () {
|
|
615
|
-
return yield* (yield* OAuth).authenticate;
|
|
616
|
-
}));
|
|
617
|
-
return {
|
|
618
|
-
url: "",
|
|
619
|
-
method: "auto",
|
|
620
|
-
instructions: "You are now authenticated!",
|
|
621
|
-
callback: async () => {
|
|
622
|
-
const accessToken = result.access_token;
|
|
623
|
-
const refreshToken = result.refresh_token;
|
|
624
|
-
const expiryDate = result.expiry_date;
|
|
625
|
-
if (!accessToken || !refreshToken || !expiryDate) return { type: "failed" };
|
|
626
|
-
return {
|
|
627
|
-
type: "success",
|
|
628
|
-
provider: config.id,
|
|
629
|
-
access: accessToken,
|
|
630
|
-
refresh: refreshToken,
|
|
631
|
-
expires: expiryDate
|
|
632
|
-
};
|
|
633
|
-
}
|
|
634
|
-
};
|
|
635
|
-
}
|
|
636
|
-
}]
|
|
637
|
-
}
|
|
638
|
-
};
|
|
639
|
-
};
|
|
640
|
-
const antigravity = async (context) => {
|
|
641
|
-
const runtime = makeRuntime({
|
|
642
|
-
openCodeCtx: context,
|
|
643
|
-
providerConfig: antigravityConfig()
|
|
644
|
-
});
|
|
645
|
-
const config = await runtime.runPromise(Effect.gen(function* () {
|
|
646
|
-
const providerConfig = yield* ProviderConfig;
|
|
647
|
-
const modelsDev = yield* fetchModelsDev;
|
|
648
|
-
return providerConfig.getConfig(modelsDev);
|
|
649
|
-
}));
|
|
650
|
-
return {
|
|
651
|
-
config: async (cfg) => {
|
|
652
|
-
cfg.provider ??= {};
|
|
653
|
-
cfg.provider[config.id] = config;
|
|
654
|
-
},
|
|
655
|
-
auth: {
|
|
656
|
-
provider: config.id,
|
|
657
|
-
loader: async (getAuth) => {
|
|
658
|
-
const auth = await getAuth();
|
|
659
|
-
if (auth.type !== "oauth") return {};
|
|
660
|
-
const credentials = {
|
|
661
|
-
access_token: auth.access,
|
|
662
|
-
refresh_token: auth.refresh,
|
|
663
|
-
expiry_date: auth.expires
|
|
664
|
-
};
|
|
665
|
-
await runtime.runPromise(Effect.gen(function* () {
|
|
666
|
-
yield* (yield* Session).setCredentials(credentials);
|
|
667
|
-
}));
|
|
668
|
-
return {
|
|
669
|
-
apiKey: "",
|
|
670
|
-
fetch: (async (input, init) => {
|
|
671
|
-
return await runtime.runPromise(customFetch(input, init));
|
|
672
|
-
})
|
|
673
|
-
};
|
|
674
|
-
},
|
|
675
|
-
methods: [{
|
|
676
|
-
type: "oauth",
|
|
677
|
-
label: "OAuth with Google",
|
|
678
|
-
authorize: async () => {
|
|
679
|
-
const result = await runtime.runPromise(Effect.gen(function* () {
|
|
680
|
-
return yield* (yield* OAuth).authenticate;
|
|
681
|
-
}));
|
|
682
|
-
return {
|
|
683
|
-
url: "",
|
|
684
|
-
method: "auto",
|
|
685
|
-
instructions: "You are now authenticated!",
|
|
686
|
-
callback: async () => {
|
|
687
|
-
const accessToken = result.access_token;
|
|
688
|
-
const refreshToken = result.refresh_token;
|
|
689
|
-
const expiryDate = result.expiry_date;
|
|
690
|
-
if (!accessToken || !refreshToken || !expiryDate) return { type: "failed" };
|
|
691
|
-
return {
|
|
692
|
-
type: "success",
|
|
693
|
-
provider: config.id,
|
|
694
|
-
access: accessToken,
|
|
695
|
-
refresh: refreshToken,
|
|
696
|
-
expires: expiryDate
|
|
697
|
-
};
|
|
698
|
-
}
|
|
699
|
-
};
|
|
700
|
-
}
|
|
701
|
-
}]
|
|
702
|
-
},
|
|
703
|
-
"chat.params": async (input, output) => {
|
|
704
|
-
await runtime.runPromise(Effect.log("chat.params event before:", input.model, output.options));
|
|
705
|
-
if (input.model.providerID === config.id) output.options = {
|
|
706
|
-
...output.options,
|
|
707
|
-
labels: { sessionId: input.sessionID }
|
|
708
|
-
};
|
|
709
|
-
await runtime.runPromise(Effect.log("chat.params event after:", input.model, output.options));
|
|
710
|
-
}
|
|
711
|
-
};
|
|
712
|
-
};
|
|
713
|
-
|
|
714
|
-
//#endregion
|
|
715
|
-
export { antigravity, geminiCli };
|
|
716
|
-
//# sourceMappingURL=main.mjs.map
|