opencode-google-auth 0.0.1
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/LICENSE +21 -0
- package/README.md +8 -0
- package/dist/main.mjs +716 -0
- package/dist/main.mjs.map +1 -0
- package/package.json +66 -0
- package/src/lib/logger.ts +43 -0
- package/src/lib/runtime.ts +34 -0
- package/src/lib/services/config.test.ts +194 -0
- package/src/lib/services/config.ts +378 -0
- package/src/lib/services/oauth.ts +135 -0
- package/src/lib/services/opencode.ts +7 -0
- package/src/lib/services/session.ts +212 -0
- package/src/main.ts +273 -0
- package/src/models.json +198 -0
- package/src/transform/request.test.ts +176 -0
- package/src/transform/request.ts +102 -0
- package/src/transform/response.test.ts +75 -0
- package/src/transform/response.ts +27 -0
- package/src/transform/stream.test.ts +108 -0
- package/src/transform/stream.ts +61 -0
- package/src/types.ts +66 -0
|
@@ -0,0 +1,212 @@
|
|
|
1
|
+
import {
|
|
2
|
+
HttpClient,
|
|
3
|
+
HttpClientRequest,
|
|
4
|
+
HttpClientResponse,
|
|
5
|
+
} from "@effect/platform"
|
|
6
|
+
import { Data, Effect, pipe, Ref, Schema } from "effect"
|
|
7
|
+
import { OAuth2Client } from "google-auth-library"
|
|
8
|
+
import type { Credentials } from "../../types"
|
|
9
|
+
import { CODE_ASSIST_VERSION, ProviderConfig } from "./config"
|
|
10
|
+
import { OpenCodeContext } from "./opencode"
|
|
11
|
+
|
|
12
|
+
export class SessionError extends Data.TaggedError("SessionError")<{
|
|
13
|
+
readonly reason:
|
|
14
|
+
| "project_fetch"
|
|
15
|
+
| "token_refresh"
|
|
16
|
+
| "no_tokens"
|
|
17
|
+
| "unauthorized"
|
|
18
|
+
readonly message: string
|
|
19
|
+
readonly cause?: unknown
|
|
20
|
+
}> {}
|
|
21
|
+
|
|
22
|
+
export class TokenExpiredError extends Data.TaggedError("TokenExpiredError")<{
|
|
23
|
+
readonly message?: string
|
|
24
|
+
}> {}
|
|
25
|
+
|
|
26
|
+
const CodeAssistTier = Schema.Struct({
|
|
27
|
+
id: Schema.String,
|
|
28
|
+
name: Schema.String,
|
|
29
|
+
description: Schema.String,
|
|
30
|
+
userDefinedCloudaicompanionProject: Schema.Boolean,
|
|
31
|
+
isDefault: Schema.optional(Schema.Boolean),
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
const LoadCodeAssistResponse = Schema.Struct({
|
|
35
|
+
currentTier: CodeAssistTier,
|
|
36
|
+
allowedTiers: Schema.Array(CodeAssistTier),
|
|
37
|
+
cloudaicompanionProject: Schema.String,
|
|
38
|
+
gcpManaged: Schema.Boolean,
|
|
39
|
+
manageSubscriptionUri: Schema.String,
|
|
40
|
+
})
|
|
41
|
+
|
|
42
|
+
export type LoadCodeAssistResponse = typeof LoadCodeAssistResponse.Type
|
|
43
|
+
|
|
44
|
+
export class Session extends Effect.Service<Session>()("Session", {
|
|
45
|
+
effect: Effect.gen(function* () {
|
|
46
|
+
const config = yield* ProviderConfig
|
|
47
|
+
const openCode = yield* OpenCodeContext
|
|
48
|
+
const httpClient = yield* HttpClient.HttpClient
|
|
49
|
+
|
|
50
|
+
const credentialsRef = yield* Ref.make<Credentials | null>(null)
|
|
51
|
+
const projectRef = yield* Ref.make<LoadCodeAssistResponse | null>(null)
|
|
52
|
+
|
|
53
|
+
const endpoint = config.ENDPOINTS.at(0) as string
|
|
54
|
+
const oauthClient = new OAuth2Client({
|
|
55
|
+
clientId: config.CLIENT_ID,
|
|
56
|
+
clientSecret: config.CLIENT_SECRET,
|
|
57
|
+
})
|
|
58
|
+
|
|
59
|
+
const getCredentials = Effect.gen(function* () {
|
|
60
|
+
const current = yield* Ref.get(credentialsRef)
|
|
61
|
+
if (!current) {
|
|
62
|
+
return yield* new SessionError({
|
|
63
|
+
reason: "no_tokens",
|
|
64
|
+
message: "No credentials set",
|
|
65
|
+
})
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
return current
|
|
69
|
+
})
|
|
70
|
+
|
|
71
|
+
const refreshTokens = Effect.gen(function* () {
|
|
72
|
+
const credentials = yield* getCredentials
|
|
73
|
+
oauthClient.setCredentials(credentials)
|
|
74
|
+
|
|
75
|
+
const result = yield* Effect.tryPromise({
|
|
76
|
+
try: () => oauthClient.refreshAccessToken(),
|
|
77
|
+
catch: (cause) =>
|
|
78
|
+
new SessionError({
|
|
79
|
+
reason: "token_refresh",
|
|
80
|
+
message: "Failed to refresh access token",
|
|
81
|
+
cause,
|
|
82
|
+
}),
|
|
83
|
+
})
|
|
84
|
+
|
|
85
|
+
const newCredentials = result.credentials
|
|
86
|
+
yield* Ref.set(credentialsRef, newCredentials as Credentials)
|
|
87
|
+
|
|
88
|
+
const accessToken = newCredentials.access_token
|
|
89
|
+
const refreshToken = newCredentials.refresh_token
|
|
90
|
+
const expiryDate = newCredentials.expiry_date
|
|
91
|
+
|
|
92
|
+
if (accessToken && refreshToken && expiryDate) {
|
|
93
|
+
yield* Effect.promise(() =>
|
|
94
|
+
openCode.client.auth.set({
|
|
95
|
+
path: { id: config.SERVICE_NAME },
|
|
96
|
+
body: {
|
|
97
|
+
type: "oauth",
|
|
98
|
+
access: accessToken,
|
|
99
|
+
refresh: refreshToken,
|
|
100
|
+
expires: expiryDate,
|
|
101
|
+
},
|
|
102
|
+
}),
|
|
103
|
+
)
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return newCredentials
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
const fetchAttempt = Effect.gen(function* () {
|
|
110
|
+
const credentials = yield* getCredentials
|
|
111
|
+
|
|
112
|
+
const request = yield* HttpClientRequest.post(
|
|
113
|
+
`${endpoint}/${CODE_ASSIST_VERSION}:loadCodeAssist`,
|
|
114
|
+
).pipe(
|
|
115
|
+
HttpClientRequest.bearerToken(credentials.access_token),
|
|
116
|
+
HttpClientRequest.bodyJson({
|
|
117
|
+
metadata: {
|
|
118
|
+
ideType: "IDE_UNSPECIFIED",
|
|
119
|
+
platform: "PLATFORM_UNSPECIFIED",
|
|
120
|
+
pluginType: "GEMINI",
|
|
121
|
+
},
|
|
122
|
+
}),
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return yield* pipe(
|
|
126
|
+
httpClient.execute(request),
|
|
127
|
+
Effect.andThen(
|
|
128
|
+
HttpClientResponse.matchStatus({
|
|
129
|
+
"2xx": (res) =>
|
|
130
|
+
HttpClientResponse.schemaBodyJson(LoadCodeAssistResponse)(res),
|
|
131
|
+
401: () => new TokenExpiredError({ message: "Token expired" }),
|
|
132
|
+
orElse: (response) =>
|
|
133
|
+
new SessionError({
|
|
134
|
+
reason: "project_fetch",
|
|
135
|
+
message: `HTTP error: ${response.status}`,
|
|
136
|
+
}),
|
|
137
|
+
}),
|
|
138
|
+
),
|
|
139
|
+
)
|
|
140
|
+
})
|
|
141
|
+
|
|
142
|
+
const fetchProject = fetchAttempt.pipe(
|
|
143
|
+
Effect.catchTag("TokenExpiredError", () =>
|
|
144
|
+
pipe(
|
|
145
|
+
Effect.log("Token expired, refreshing..."),
|
|
146
|
+
Effect.flatMap(() => refreshTokens),
|
|
147
|
+
Effect.flatMap(() => fetchAttempt),
|
|
148
|
+
),
|
|
149
|
+
),
|
|
150
|
+
Effect.catchAll((error) => {
|
|
151
|
+
if (error instanceof SessionError) {
|
|
152
|
+
return Effect.fail(error)
|
|
153
|
+
}
|
|
154
|
+
return Effect.fail(
|
|
155
|
+
new SessionError({
|
|
156
|
+
reason: "project_fetch",
|
|
157
|
+
message: "Failed to fetch project",
|
|
158
|
+
cause: error,
|
|
159
|
+
}),
|
|
160
|
+
)
|
|
161
|
+
}),
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
const ensureProject = Effect.gen(function* () {
|
|
165
|
+
const cached = yield* Ref.get(projectRef)
|
|
166
|
+
if (cached !== null) {
|
|
167
|
+
return cached
|
|
168
|
+
}
|
|
169
|
+
const project = yield* fetchProject
|
|
170
|
+
yield* Ref.set(projectRef, project)
|
|
171
|
+
return project
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
const getAccessToken = Effect.gen(function* () {
|
|
175
|
+
const currentCreds = yield* Ref.get(credentialsRef)
|
|
176
|
+
|
|
177
|
+
if (!currentCreds?.access_token) {
|
|
178
|
+
return yield* new SessionError({
|
|
179
|
+
reason: "no_tokens",
|
|
180
|
+
message: "No access token available",
|
|
181
|
+
})
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const buffer = 5 * 60 * 1000
|
|
185
|
+
const isExpired = (currentCreds.expiry_date ?? 0) < Date.now() + buffer
|
|
186
|
+
|
|
187
|
+
let accessToken = currentCreds.access_token
|
|
188
|
+
|
|
189
|
+
if (isExpired) {
|
|
190
|
+
yield* Effect.log("Access token expired, refreshing...")
|
|
191
|
+
const refreshed = yield* refreshTokens
|
|
192
|
+
if (!refreshed.access_token) {
|
|
193
|
+
return yield* new SessionError({
|
|
194
|
+
reason: "token_refresh",
|
|
195
|
+
message: "Refresh did not return access token",
|
|
196
|
+
})
|
|
197
|
+
}
|
|
198
|
+
accessToken = refreshed.access_token
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
yield* ensureProject
|
|
202
|
+
return accessToken
|
|
203
|
+
})
|
|
204
|
+
|
|
205
|
+
return {
|
|
206
|
+
setCredentials: (credentials: Credentials) =>
|
|
207
|
+
Ref.set(credentialsRef, credentials),
|
|
208
|
+
getAccessToken,
|
|
209
|
+
ensureProject,
|
|
210
|
+
}
|
|
211
|
+
}),
|
|
212
|
+
}) {}
|
package/src/main.ts
ADDED
|
@@ -0,0 +1,273 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
GoogleGenerativeAIProviderOptions,
|
|
3
|
+
GoogleGenerativeAIProviderSettings,
|
|
4
|
+
} from "@ai-sdk/google"
|
|
5
|
+
import { HttpClient } from "@effect/platform"
|
|
6
|
+
import type { Plugin } from "@opencode-ai/plugin"
|
|
7
|
+
import { Effect } from "effect"
|
|
8
|
+
import { makeRuntime } from "./lib/runtime"
|
|
9
|
+
import {
|
|
10
|
+
antigravityConfig,
|
|
11
|
+
geminiCliConfig,
|
|
12
|
+
ProviderConfig,
|
|
13
|
+
} from "./lib/services/config"
|
|
14
|
+
import { OAuth } from "./lib/services/oauth"
|
|
15
|
+
import { Session } from "./lib/services/session"
|
|
16
|
+
import { transformRequest } from "./transform/request"
|
|
17
|
+
import { transformNonStreamingResponse } from "./transform/response"
|
|
18
|
+
import { transformStreamingResponse } from "./transform/stream"
|
|
19
|
+
import type { Credentials, ModelsDev } from "./types"
|
|
20
|
+
|
|
21
|
+
const fetchModelsDev = Effect.gen(function* () {
|
|
22
|
+
const client = yield* HttpClient.HttpClient
|
|
23
|
+
const response = yield* client.get("https://models.dev/api.json")
|
|
24
|
+
return (yield* response.json) as ModelsDev
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
const customFetch = Effect.fn(function* (
|
|
28
|
+
input: Parameters<typeof fetch>[0],
|
|
29
|
+
init: Parameters<typeof fetch>[1],
|
|
30
|
+
) {
|
|
31
|
+
const config = yield* ProviderConfig
|
|
32
|
+
|
|
33
|
+
let lastResponse: Response | null = null
|
|
34
|
+
|
|
35
|
+
for (const endpoint of config.ENDPOINTS) {
|
|
36
|
+
const result = yield* transformRequest(input, init, endpoint)
|
|
37
|
+
|
|
38
|
+
const { request, ...loggedBody } = JSON.parse(result.init.body as string)
|
|
39
|
+
const generationConfig = request.generationConfig
|
|
40
|
+
|
|
41
|
+
yield* Effect.log(
|
|
42
|
+
"Transformed request (Omitting request except generationConfig) :",
|
|
43
|
+
result.streaming,
|
|
44
|
+
result.input,
|
|
45
|
+
{ ...loggedBody, request: { generationConfig } },
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
const response = yield* Effect.promise(() =>
|
|
49
|
+
fetch(result.input, result.init),
|
|
50
|
+
)
|
|
51
|
+
|
|
52
|
+
// On 429 or 403, try next endpoint
|
|
53
|
+
if (response.status === 429 || response.status === 403) {
|
|
54
|
+
yield* Effect.log(`${response.status} on ${endpoint}, trying next...`)
|
|
55
|
+
lastResponse = response
|
|
56
|
+
continue
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
if (!response.ok) {
|
|
60
|
+
const cloned = response.clone()
|
|
61
|
+
const clonedJson = yield* Effect.promise(() => cloned.json())
|
|
62
|
+
|
|
63
|
+
yield* Effect.log(
|
|
64
|
+
"Received response:",
|
|
65
|
+
cloned.status,
|
|
66
|
+
clonedJson,
|
|
67
|
+
cloned.headers,
|
|
68
|
+
)
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
return result.streaming ?
|
|
72
|
+
transformStreamingResponse(response)
|
|
73
|
+
: yield* Effect.promise(() => transformNonStreamingResponse(response))
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
// All endpoints exhausted with 429
|
|
77
|
+
yield* Effect.logWarning("All endpoints rate limited (429)")
|
|
78
|
+
return lastResponse as Response
|
|
79
|
+
}, Effect.tapDefect(Effect.logError))
|
|
80
|
+
|
|
81
|
+
export const geminiCli: Plugin = async (context) => {
|
|
82
|
+
const runtime = makeRuntime({
|
|
83
|
+
openCodeCtx: context,
|
|
84
|
+
providerConfig: geminiCliConfig(),
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
const config = await runtime.runPromise(
|
|
88
|
+
Effect.gen(function* () {
|
|
89
|
+
const providerConfig = yield* ProviderConfig
|
|
90
|
+
const modelsDev = yield* fetchModelsDev
|
|
91
|
+
|
|
92
|
+
return providerConfig.getConfig(modelsDev)
|
|
93
|
+
}),
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
return {
|
|
97
|
+
config: async (cfg) => {
|
|
98
|
+
cfg.provider ??= {}
|
|
99
|
+
cfg.provider[config.id as string] = config
|
|
100
|
+
},
|
|
101
|
+
auth: {
|
|
102
|
+
provider: config.id as string,
|
|
103
|
+
loader: async (getAuth) => {
|
|
104
|
+
const auth = await getAuth()
|
|
105
|
+
if (auth.type !== "oauth") return {}
|
|
106
|
+
|
|
107
|
+
const credentials: Credentials = {
|
|
108
|
+
access_token: auth.access,
|
|
109
|
+
refresh_token: auth.refresh,
|
|
110
|
+
expiry_date: auth.expires,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
await runtime.runPromise(
|
|
114
|
+
Effect.gen(function* () {
|
|
115
|
+
const session = yield* Session
|
|
116
|
+
yield* session.setCredentials(credentials)
|
|
117
|
+
}),
|
|
118
|
+
)
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
apiKey: "",
|
|
122
|
+
fetch: (async (input, init) => {
|
|
123
|
+
const response = await runtime.runPromise(customFetch(input, init))
|
|
124
|
+
return response
|
|
125
|
+
}) as typeof fetch,
|
|
126
|
+
} satisfies GoogleGenerativeAIProviderSettings
|
|
127
|
+
},
|
|
128
|
+
methods: [
|
|
129
|
+
{
|
|
130
|
+
type: "oauth",
|
|
131
|
+
label: "OAuth with Google",
|
|
132
|
+
authorize: async () => {
|
|
133
|
+
const result = await runtime.runPromise(
|
|
134
|
+
Effect.gen(function* () {
|
|
135
|
+
const oauth = yield* OAuth
|
|
136
|
+
return yield* oauth.authenticate
|
|
137
|
+
}),
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
url: "",
|
|
142
|
+
method: "auto",
|
|
143
|
+
instructions: "You are now authenticated!",
|
|
144
|
+
callback: async () => {
|
|
145
|
+
const accessToken = result.access_token
|
|
146
|
+
const refreshToken = result.refresh_token
|
|
147
|
+
const expiryDate = result.expiry_date
|
|
148
|
+
|
|
149
|
+
if (!accessToken || !refreshToken || !expiryDate) {
|
|
150
|
+
return { type: "failed" }
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return {
|
|
154
|
+
type: "success",
|
|
155
|
+
provider: config.id as string,
|
|
156
|
+
access: accessToken,
|
|
157
|
+
refresh: refreshToken,
|
|
158
|
+
expires: expiryDate,
|
|
159
|
+
}
|
|
160
|
+
},
|
|
161
|
+
}
|
|
162
|
+
},
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
},
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
export const antigravity: Plugin = async (context) => {
|
|
170
|
+
const runtime = makeRuntime({
|
|
171
|
+
openCodeCtx: context,
|
|
172
|
+
providerConfig: antigravityConfig(),
|
|
173
|
+
})
|
|
174
|
+
|
|
175
|
+
const config = await runtime.runPromise(
|
|
176
|
+
Effect.gen(function* () {
|
|
177
|
+
const providerConfig = yield* ProviderConfig
|
|
178
|
+
const modelsDev = yield* fetchModelsDev
|
|
179
|
+
|
|
180
|
+
return providerConfig.getConfig(modelsDev)
|
|
181
|
+
}),
|
|
182
|
+
)
|
|
183
|
+
|
|
184
|
+
return {
|
|
185
|
+
config: async (cfg) => {
|
|
186
|
+
cfg.provider ??= {}
|
|
187
|
+
cfg.provider[config.id as string] = config
|
|
188
|
+
},
|
|
189
|
+
auth: {
|
|
190
|
+
provider: config.id as string,
|
|
191
|
+
loader: async (getAuth) => {
|
|
192
|
+
const auth = await getAuth()
|
|
193
|
+
if (auth.type !== "oauth") return {}
|
|
194
|
+
|
|
195
|
+
const credentials: Credentials = {
|
|
196
|
+
access_token: auth.access,
|
|
197
|
+
refresh_token: auth.refresh,
|
|
198
|
+
expiry_date: auth.expires,
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
await runtime.runPromise(
|
|
202
|
+
Effect.gen(function* () {
|
|
203
|
+
const session = yield* Session
|
|
204
|
+
yield* session.setCredentials(credentials)
|
|
205
|
+
}),
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
apiKey: "",
|
|
210
|
+
fetch: (async (input, init) => {
|
|
211
|
+
const response = await runtime.runPromise(customFetch(input, init))
|
|
212
|
+
return response
|
|
213
|
+
}) as typeof fetch,
|
|
214
|
+
} satisfies GoogleGenerativeAIProviderSettings
|
|
215
|
+
},
|
|
216
|
+
methods: [
|
|
217
|
+
{
|
|
218
|
+
type: "oauth",
|
|
219
|
+
label: "OAuth with Google",
|
|
220
|
+
authorize: async () => {
|
|
221
|
+
const result = await runtime.runPromise(
|
|
222
|
+
Effect.gen(function* () {
|
|
223
|
+
const oauth = yield* OAuth
|
|
224
|
+
return yield* oauth.authenticate
|
|
225
|
+
}),
|
|
226
|
+
)
|
|
227
|
+
|
|
228
|
+
return {
|
|
229
|
+
url: "",
|
|
230
|
+
method: "auto",
|
|
231
|
+
instructions: "You are now authenticated!",
|
|
232
|
+
callback: async () => {
|
|
233
|
+
const accessToken = result.access_token
|
|
234
|
+
const refreshToken = result.refresh_token
|
|
235
|
+
const expiryDate = result.expiry_date
|
|
236
|
+
|
|
237
|
+
if (!accessToken || !refreshToken || !expiryDate) {
|
|
238
|
+
return { type: "failed" }
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
type: "success",
|
|
243
|
+
provider: config.id as string,
|
|
244
|
+
access: accessToken,
|
|
245
|
+
refresh: refreshToken,
|
|
246
|
+
expires: expiryDate,
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
}
|
|
250
|
+
},
|
|
251
|
+
},
|
|
252
|
+
],
|
|
253
|
+
},
|
|
254
|
+
"chat.params": async (input, output) => {
|
|
255
|
+
await runtime.runPromise(
|
|
256
|
+
Effect.log("chat.params event before:", input.model, output.options),
|
|
257
|
+
)
|
|
258
|
+
|
|
259
|
+
if (input.model.providerID === config.id) {
|
|
260
|
+
output.options = {
|
|
261
|
+
...output.options,
|
|
262
|
+
labels: {
|
|
263
|
+
sessionId: input.sessionID,
|
|
264
|
+
},
|
|
265
|
+
} satisfies GoogleGenerativeAIProviderOptions
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
await runtime.runPromise(
|
|
269
|
+
Effect.log("chat.params event after:", input.model, output.options),
|
|
270
|
+
)
|
|
271
|
+
},
|
|
272
|
+
}
|
|
273
|
+
}
|
package/src/models.json
ADDED
|
@@ -0,0 +1,198 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "google",
|
|
3
|
+
"env": [
|
|
4
|
+
"GOOGLE_GENERATIVE_AI_API_KEY",
|
|
5
|
+
"GEMINI_API_KEY"
|
|
6
|
+
],
|
|
7
|
+
"npm": "@ai-sdk/google",
|
|
8
|
+
"name": "Google",
|
|
9
|
+
"doc": "https://ai.google.dev/gemini-api/docs/pricing",
|
|
10
|
+
"models": {
|
|
11
|
+
"gemini-3-flash-preview": {
|
|
12
|
+
"id": "gemini-3-flash-preview",
|
|
13
|
+
"name": "Gemini 3 Flash Preview",
|
|
14
|
+
"family": "gemini-flash",
|
|
15
|
+
"attachment": true,
|
|
16
|
+
"reasoning": true,
|
|
17
|
+
"tool_call": true,
|
|
18
|
+
"structured_output": true,
|
|
19
|
+
"temperature": true,
|
|
20
|
+
"knowledge": "2025-01",
|
|
21
|
+
"release_date": "2025-12-17",
|
|
22
|
+
"last_updated": "2025-12-17",
|
|
23
|
+
"modalities": {
|
|
24
|
+
"input": [
|
|
25
|
+
"text",
|
|
26
|
+
"image",
|
|
27
|
+
"video",
|
|
28
|
+
"audio",
|
|
29
|
+
"pdf"
|
|
30
|
+
],
|
|
31
|
+
"output": [
|
|
32
|
+
"text"
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
"open_weights": false,
|
|
36
|
+
"cost": {
|
|
37
|
+
"input": 0.5,
|
|
38
|
+
"output": 3,
|
|
39
|
+
"cache_read": 0.05,
|
|
40
|
+
"context_over_200k": {
|
|
41
|
+
"input": 0.5,
|
|
42
|
+
"output": 3,
|
|
43
|
+
"cache_read": 0.05
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
"limit": {
|
|
47
|
+
"context": 1048576,
|
|
48
|
+
"output": 65536
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
"gemini-3-pro-preview": {
|
|
52
|
+
"id": "gemini-3-pro-preview",
|
|
53
|
+
"name": "Gemini 3 Pro Preview",
|
|
54
|
+
"family": "gemini-pro",
|
|
55
|
+
"attachment": true,
|
|
56
|
+
"reasoning": true,
|
|
57
|
+
"tool_call": true,
|
|
58
|
+
"structured_output": true,
|
|
59
|
+
"temperature": true,
|
|
60
|
+
"knowledge": "2025-01",
|
|
61
|
+
"release_date": "2025-11-18",
|
|
62
|
+
"last_updated": "2025-11-18",
|
|
63
|
+
"modalities": {
|
|
64
|
+
"input": [
|
|
65
|
+
"text",
|
|
66
|
+
"image",
|
|
67
|
+
"video",
|
|
68
|
+
"audio",
|
|
69
|
+
"pdf"
|
|
70
|
+
],
|
|
71
|
+
"output": [
|
|
72
|
+
"text"
|
|
73
|
+
]
|
|
74
|
+
},
|
|
75
|
+
"open_weights": false,
|
|
76
|
+
"cost": {
|
|
77
|
+
"input": 2,
|
|
78
|
+
"output": 12,
|
|
79
|
+
"cache_read": 0.2,
|
|
80
|
+
"context_over_200k": {
|
|
81
|
+
"input": 4,
|
|
82
|
+
"output": 18,
|
|
83
|
+
"cache_read": 0.4
|
|
84
|
+
}
|
|
85
|
+
},
|
|
86
|
+
"limit": {
|
|
87
|
+
"context": 1000000,
|
|
88
|
+
"output": 64000
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
"gemini-2.5-flash": {
|
|
92
|
+
"id": "gemini-2.5-flash",
|
|
93
|
+
"name": "Gemini 2.5 Flash",
|
|
94
|
+
"family": "gemini-flash",
|
|
95
|
+
"attachment": true,
|
|
96
|
+
"reasoning": true,
|
|
97
|
+
"tool_call": true,
|
|
98
|
+
"structured_output": true,
|
|
99
|
+
"temperature": true,
|
|
100
|
+
"knowledge": "2025-01",
|
|
101
|
+
"release_date": "2025-03-20",
|
|
102
|
+
"last_updated": "2025-06-05",
|
|
103
|
+
"modalities": {
|
|
104
|
+
"input": [
|
|
105
|
+
"text",
|
|
106
|
+
"image",
|
|
107
|
+
"audio",
|
|
108
|
+
"video",
|
|
109
|
+
"pdf"
|
|
110
|
+
],
|
|
111
|
+
"output": [
|
|
112
|
+
"text"
|
|
113
|
+
]
|
|
114
|
+
},
|
|
115
|
+
"open_weights": false,
|
|
116
|
+
"cost": {
|
|
117
|
+
"input": 0.3,
|
|
118
|
+
"output": 2.5,
|
|
119
|
+
"cache_read": 0.075,
|
|
120
|
+
"input_audio": 1
|
|
121
|
+
},
|
|
122
|
+
"limit": {
|
|
123
|
+
"context": 1048576,
|
|
124
|
+
"output": 65536
|
|
125
|
+
}
|
|
126
|
+
},
|
|
127
|
+
"gemini-2.5-flash-lite": {
|
|
128
|
+
"id": "gemini-2.5-flash-lite",
|
|
129
|
+
"name": "Gemini 2.5 Flash Lite",
|
|
130
|
+
"family": "gemini-flash-lite",
|
|
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-06-17",
|
|
138
|
+
"last_updated": "2025-06-17",
|
|
139
|
+
"modalities": {
|
|
140
|
+
"input": [
|
|
141
|
+
"text",
|
|
142
|
+
"image",
|
|
143
|
+
"audio",
|
|
144
|
+
"video",
|
|
145
|
+
"pdf"
|
|
146
|
+
],
|
|
147
|
+
"output": [
|
|
148
|
+
"text"
|
|
149
|
+
]
|
|
150
|
+
},
|
|
151
|
+
"open_weights": false,
|
|
152
|
+
"cost": {
|
|
153
|
+
"input": 0.1,
|
|
154
|
+
"output": 0.4,
|
|
155
|
+
"cache_read": 0.025
|
|
156
|
+
},
|
|
157
|
+
"limit": {
|
|
158
|
+
"context": 1048576,
|
|
159
|
+
"output": 65536
|
|
160
|
+
}
|
|
161
|
+
},
|
|
162
|
+
"gemini-2.5-pro": {
|
|
163
|
+
"id": "gemini-2.5-pro",
|
|
164
|
+
"name": "Gemini 2.5 Pro",
|
|
165
|
+
"family": "gemini-pro",
|
|
166
|
+
"attachment": true,
|
|
167
|
+
"reasoning": true,
|
|
168
|
+
"tool_call": true,
|
|
169
|
+
"structured_output": true,
|
|
170
|
+
"temperature": true,
|
|
171
|
+
"knowledge": "2025-01",
|
|
172
|
+
"release_date": "2025-03-20",
|
|
173
|
+
"last_updated": "2025-06-05",
|
|
174
|
+
"modalities": {
|
|
175
|
+
"input": [
|
|
176
|
+
"text",
|
|
177
|
+
"image",
|
|
178
|
+
"audio",
|
|
179
|
+
"video",
|
|
180
|
+
"pdf"
|
|
181
|
+
],
|
|
182
|
+
"output": [
|
|
183
|
+
"text"
|
|
184
|
+
]
|
|
185
|
+
},
|
|
186
|
+
"open_weights": false,
|
|
187
|
+
"cost": {
|
|
188
|
+
"input": 1.25,
|
|
189
|
+
"output": 10,
|
|
190
|
+
"cache_read": 0.31
|
|
191
|
+
},
|
|
192
|
+
"limit": {
|
|
193
|
+
"context": 1048576,
|
|
194
|
+
"output": 65536
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|