Skip to content

Commit 2a7f52f

Browse files
committed
vertex gemini consolidation
1 parent c3c506c commit 2a7f52f

File tree

13 files changed

+969
-1825
lines changed

13 files changed

+969
-1825
lines changed

apps/sim/app/workspace/[workspaceId]/w/[workflowId]/components/panel/components/editor/components/sub-block/components/credential-selector/components/oauth-required-modal.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,8 @@ const SCOPE_DESCRIPTIONS: Record<string, string> = {
4343
'https://www.googleapis.com/auth/admin.directory.group.readonly': 'View Google Workspace groups',
4444
'https://www.googleapis.com/auth/admin.directory.group.member.readonly':
4545
'View Google Workspace group memberships',
46+
'https://www.googleapis.com/auth/cloud-platform':
47+
'Full access to Google Cloud resources for Vertex AI',
4648
'read:confluence-content.all': 'Read all Confluence content',
4749
'read:confluence-space.summary': 'Read Confluence space information',
4850
'read:space:confluence': 'View Confluence spaces',

apps/sim/blocks/blocks/agent.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -314,6 +314,19 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
314314
value: providers.vertex.models,
315315
},
316316
},
317+
{
318+
id: 'vertexCredential',
319+
title: 'Google Cloud Account',
320+
type: 'oauth-input',
321+
serviceId: 'vertex-ai',
322+
requiredScopes: ['https://www.googleapis.com/auth/cloud-platform'],
323+
placeholder: 'Select Google Cloud account',
324+
required: true,
325+
condition: {
326+
field: 'model',
327+
value: providers.vertex.models,
328+
},
329+
},
317330
{
318331
id: 'tools',
319332
title: 'Tools',
@@ -328,17 +341,21 @@ export const AgentBlock: BlockConfig<AgentResponse> = {
328341
password: true,
329342
connectionDroppable: false,
330343
required: true,
331-
// Hide API key for hosted models, Ollama models, and vLLM models
344+
// Hide API key for hosted models, Ollama models, vLLM models, and Vertex models (uses OAuth)
332345
condition: isHosted
333346
? {
334347
field: 'model',
335-
value: getHostedModels(),
348+
value: [...getHostedModels(), ...providers.vertex.models],
336349
not: true, // Show for all models EXCEPT those listed
337350
}
338351
: () => ({
339352
field: 'model',
340-
value: [...getCurrentOllamaModels(), ...getCurrentVLLMModels()],
341-
not: true, // Show for all models EXCEPT Ollama and vLLM models
353+
value: [
354+
...getCurrentOllamaModels(),
355+
...getCurrentVLLMModels(),
356+
...providers.vertex.models,
357+
],
358+
not: true, // Show for all models EXCEPT Ollama, vLLM, and Vertex models
342359
}),
343360
},
344361
{

apps/sim/executor/handlers/agent/agent-handler.ts

Lines changed: 41 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import { db } from '@sim/db'
2-
import { mcpServers } from '@sim/db/schema'
2+
import { account, mcpServers } from '@sim/db/schema'
33
import { and, eq, inArray, isNull } from 'drizzle-orm'
44
import { createLogger } from '@/lib/logs/console/logger'
55
import { createMcpToolId } from '@/lib/mcp/utils'
6+
import { refreshTokenIfNeeded } from '@/app/api/auth/oauth/utils'
67
import { getAllBlocks } from '@/blocks'
78
import type { BlockOutput } from '@/blocks/types'
89
import { AGENT, BlockType, DEFAULTS, HTTP } from '@/executor/constants'
@@ -919,6 +920,7 @@ export class AgentBlockHandler implements BlockHandler {
919920
azureApiVersion: inputs.azureApiVersion,
920921
vertexProject: inputs.vertexProject,
921922
vertexLocation: inputs.vertexLocation,
923+
vertexCredential: inputs.vertexCredential,
922924
responseFormat,
923925
workflowId: ctx.workflowId,
924926
workspaceId: ctx.workspaceId,
@@ -997,7 +999,17 @@ export class AgentBlockHandler implements BlockHandler {
997999
responseFormat: any,
9981000
providerStartTime: number
9991001
) {
1000-
const finalApiKey = this.getApiKey(providerId, model, providerRequest.apiKey)
1002+
let finalApiKey: string
1003+
1004+
// For Vertex AI, resolve OAuth credential to access token
1005+
if (providerId === 'vertex' && providerRequest.vertexCredential) {
1006+
finalApiKey = await this.resolveVertexCredential(
1007+
providerRequest.vertexCredential,
1008+
ctx.workflowId
1009+
)
1010+
} else {
1011+
finalApiKey = this.getApiKey(providerId, model, providerRequest.apiKey)
1012+
}
10011013

10021014
const { blockData, blockNameMapping } = collectBlockData(ctx)
10031015

@@ -1024,7 +1036,6 @@ export class AgentBlockHandler implements BlockHandler {
10241036
blockNameMapping,
10251037
})
10261038

1027-
this.logExecutionSuccess(providerId, model, ctx, block, providerStartTime, response)
10281039
return this.processProviderResponse(response, block, responseFormat)
10291040
}
10301041

@@ -1049,15 +1060,6 @@ export class AgentBlockHandler implements BlockHandler {
10491060
throw new Error(errorMessage)
10501061
}
10511062

1052-
this.logExecutionSuccess(
1053-
providerRequest.provider,
1054-
providerRequest.model,
1055-
ctx,
1056-
block,
1057-
providerStartTime,
1058-
'HTTP response'
1059-
)
1060-
10611063
const contentType = response.headers.get('Content-Type')
10621064
if (contentType?.includes(HTTP.CONTENT_TYPE.EVENT_STREAM)) {
10631065
return this.handleStreamingResponse(response, block, ctx, inputs)
@@ -1117,21 +1119,33 @@ export class AgentBlockHandler implements BlockHandler {
11171119
}
11181120
}
11191121

1120-
private logExecutionSuccess(
1121-
provider: string,
1122-
model: string,
1123-
ctx: ExecutionContext,
1124-
block: SerializedBlock,
1125-
startTime: number,
1126-
response: any
1127-
) {
1128-
const executionTime = Date.now() - startTime
1129-
const responseType =
1130-
response instanceof ReadableStream
1131-
? 'stream'
1132-
: response && typeof response === 'object' && 'stream' in response
1133-
? 'streaming-execution'
1134-
: 'json'
1122+
/**
1123+
* Resolves a Vertex AI OAuth credential to an access token
1124+
*/
1125+
private async resolveVertexCredential(credentialId: string, workflowId: string): Promise<string> {
1126+
const requestId = `vertex-${Date.now()}`
1127+
1128+
logger.info(`[${requestId}] Resolving Vertex AI credential: ${credentialId}`)
1129+
1130+
// Get the credential - we need to find the owner
1131+
// Since we're in a workflow context, we can query the credential directly
1132+
const credential = await db.query.account.findFirst({
1133+
where: eq(account.id, credentialId),
1134+
})
1135+
1136+
if (!credential) {
1137+
throw new Error(`Vertex AI credential not found: ${credentialId}`)
1138+
}
1139+
1140+
// Refresh the token if needed
1141+
const { accessToken } = await refreshTokenIfNeeded(requestId, credential, credentialId)
1142+
1143+
if (!accessToken) {
1144+
throw new Error('Failed to get Vertex AI access token')
1145+
}
1146+
1147+
logger.info(`[${requestId}] Successfully resolved Vertex AI credential`)
1148+
return accessToken
11351149
}
11361150

11371151
private handleExecutionError(

apps/sim/executor/handlers/agent/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ export interface AgentInputs {
2121
azureApiVersion?: string
2222
vertexProject?: string
2323
vertexLocation?: string
24+
vertexCredential?: string
2425
reasoningEffort?: string
2526
verbosity?: string
2627
}

apps/sim/lib/auth/auth.ts

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,6 +579,21 @@ export const auth = betterAuth({
579579
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/google-groups`,
580580
},
581581

582+
{
583+
providerId: 'vertex-ai',
584+
clientId: env.GOOGLE_CLIENT_ID as string,
585+
clientSecret: env.GOOGLE_CLIENT_SECRET as string,
586+
discoveryUrl: 'https://accounts.google.com/.well-known/openid-configuration',
587+
accessType: 'offline',
588+
scopes: [
589+
'https://www.googleapis.com/auth/userinfo.email',
590+
'https://www.googleapis.com/auth/userinfo.profile',
591+
'https://www.googleapis.com/auth/cloud-platform',
592+
],
593+
prompt: 'consent',
594+
redirectURI: `${getBaseUrl()}/api/auth/oauth2/callback/vertex-ai`,
595+
},
596+
582597
{
583598
providerId: 'microsoft-teams',
584599
clientId: env.MICROSOFT_CLIENT_ID as string,

apps/sim/lib/oauth/oauth.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import {
3232
SlackIcon,
3333
SpotifyIcon,
3434
TrelloIcon,
35+
VertexIcon,
3536
WealthboxIcon,
3637
WebflowIcon,
3738
WordpressIcon,
@@ -80,6 +81,7 @@ export type OAuthService =
8081
| 'google-vault'
8182
| 'google-forms'
8283
| 'google-groups'
84+
| 'vertex-ai'
8385
| 'github'
8486
| 'x'
8587
| 'confluence'
@@ -237,6 +239,16 @@ export const OAUTH_PROVIDERS: Record<string, OAuthProviderConfig> = {
237239
],
238240
scopeHints: ['admin.directory.group'],
239241
},
242+
'vertex-ai': {
243+
id: 'vertex-ai',
244+
name: 'Vertex AI',
245+
description: 'Access Google Cloud Vertex AI for Gemini models with OAuth.',
246+
providerId: 'vertex-ai',
247+
icon: (props) => VertexIcon(props),
248+
baseProviderIcon: (props) => VertexIcon(props),
249+
scopes: ['https://www.googleapis.com/auth/cloud-platform'],
250+
scopeHints: ['cloud-platform', 'vertex', 'aiplatform'],
251+
},
240252
},
241253
defaultService: 'gmail',
242254
},
@@ -1099,6 +1111,12 @@ export function parseProvider(provider: OAuthProvider): ProviderConfig {
10991111
featureType: 'microsoft-planner',
11001112
}
11011113
}
1114+
if (provider === 'vertex-ai') {
1115+
return {
1116+
baseProvider: 'google',
1117+
featureType: 'vertex-ai',
1118+
}
1119+
}
11021120

11031121
// Handle compound providers (e.g., 'google-email' -> { baseProvider: 'google', featureType: 'email' })
11041122
const [base, feature] = provider.split('-')
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import { GoogleGenAI } from '@google/genai'
2+
import { createLogger } from '@/lib/logs/console/logger'
3+
import type { GeminiClientConfig } from './types'
4+
5+
const logger = createLogger('GeminiClient')
6+
7+
/**
8+
* Creates a GoogleGenAI client configured for either Google Gemini API or Vertex AI
9+
*
10+
* For Google Gemini API:
11+
* - Uses API key authentication
12+
*
13+
* For Vertex AI:
14+
* - Uses OAuth access token via HTTP Authorization header
15+
* - Requires project and location
16+
*/
17+
export function createGeminiClient(config: GeminiClientConfig): GoogleGenAI {
18+
if (config.vertexai) {
19+
if (!config.project) {
20+
throw new Error('Vertex AI requires a project ID')
21+
}
22+
if (!config.accessToken) {
23+
throw new Error('Vertex AI requires an access token')
24+
}
25+
26+
const location = config.location ?? 'us-central1'
27+
28+
logger.info('Creating Vertex AI client', {
29+
project: config.project,
30+
location,
31+
hasAccessToken: !!config.accessToken,
32+
})
33+
34+
// Create client with Vertex AI configuration
35+
// Use httpOptions.headers to pass the access token directly
36+
return new GoogleGenAI({
37+
vertexai: true,
38+
project: config.project,
39+
location,
40+
httpOptions: {
41+
headers: {
42+
Authorization: `Bearer ${config.accessToken}`,
43+
},
44+
},
45+
})
46+
}
47+
48+
// Google Gemini API with API key
49+
if (!config.apiKey) {
50+
throw new Error('Google Gemini API requires an API key')
51+
}
52+
53+
logger.info('Creating Google Gemini client')
54+
55+
return new GoogleGenAI({
56+
apiKey: config.apiKey,
57+
})
58+
}

0 commit comments

Comments
 (0)