-
-
Notifications
You must be signed in to change notification settings - Fork 65
✨ Add Vertex AI as a provider #136
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
@subpop, thank you for your contribution, I appreciate it! ✨
Please let me know if I can provide any help |
This is a very good question. My employer has provided our company with access to Claude Code by way of hosting the Claude models on GCP Vertex. We are unable to use Claude directly with an Anthropic API key. It's, admittedly, an unusual setup, but I've found it manageable as long as clients offer GCP Vertex as a provider (Goose, for example, offers Gemini, Claude, and GCP Vertex). GCP Vertex will never be a featured provider, but it does offer access to models in cases like mine. I'd like to revisit the implementation a bit more. Maybe there's more consolidation opportunity between Vertex and Gemini. If their APIs are similar enough, maybe a generic "VertexHandler" class could carry much of the implementation, and then a derived "GeminiHandler" and a "CustomHandler" could be offered as concrete implementations. I'll dig into that more.
I do not have a paid Developer account, and so my profile cannot create CloudKit containers and add CloudKit entitlements to a project. I cannot compile it at all.
I'll take a look and rebase my changes as best I can. |
|
@subpop Thank you for the explanation. I think we could add support for VertexAI indeed.
2. (if not done yet) set the signing settings and blank provisioning profile.
Please let me know in case of any further questions |
Combine common functionality between GeminiHandler and VertexAIHandler into a common GeminiHandlerBase class. GeminiHandler subclasses that and adds a concrete Gemini provider using the production Gemini URLs and an API key. When creating a Vertex AI provider, two possible handler classes are used. If the model name begins with “gemini”, a VertexGeminiHandler is used. If the model begins with “claude” a separate VertexClaudeHandler is used. Those two handlers reuse existing Gemini and Claude response types.
|
Thanks for that second entitlement file. It worked out great. I'm able to build and run it again. I rebased this branch on |
Renset
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop I've completed the first review stage.
In general, it works - yay (it actually took me some time to get used to GCP/Vertex AI, but I finally figured it out)!
However, it requires some reworking:
- See my comments on the lines.
- Some changes have been made to the main branch, including the addition of a fix for a critical bug in Gemini Handler. Also, significant changes are expected with the upcoming merge of the pdf-uploads branch into the main branch. Yes, PDF file attachments will now be supported, including Gemini Handler. You can also migrate this support to VertexAI, which would be great.
What I am suggesting:
Wait up to seven days for the merge of the pdf-uploads branch into the main branch. If my internal tests are successful, I don't expect any breaking changes to GeminiHandler in the near future. I'll post another update here to keep you informed.
After that, sync with the main branch again and implement the necessary fixes. I believe all the fixes are mandatory. Feel free to use Claude Code or another AI agent.
Note that I have limited experience with GCP/Vertex AI, so I can't properly test it. Please test intensively with different models, especially new ones like Gemini 3 Pro, and make sure it passes the "pizza test."
Let me know if you need any assistance.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop please update to Vertex AI icon (https://commons.wikimedia.org/wiki/File:Vertex_AI_Logo.svg). Don't forget to make it black-n-white (let me know if you need help with that)
| apiModelRef: "https://cloud.google.com/vertex-ai/generative-ai/docs/learn/models", | ||
| defaultModel: "gemini-2.5-pro", | ||
| models: [ | ||
| "gemini-2.5-pro", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop please use up to date models here like gemini-3-pro-preview etc.
| } | ||
| } | ||
| } | ||
| vertexActiveDataTask = task |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop Claude Opus finding:
Assignment after task creation may cause potential race condition here (there's a window where the task assignment happens after cancellation check)
| var apiUrl: URL { get set } | ||
| var apiKey: String { get set } | ||
| var model: String { get set } | ||
| var gcpProjectId: String? { get set } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop I think adding the gcpProjectId and gcpRegion to the APIServiceConfiguration as is is unnecessary because they are specific to the VertexAI provider. Instead, consider adding a new dictionary providerOptions:
protocol APIServiceConfiguration {
var name: String { get set }
var apiUrl: URL { get set }
var apiKey: String { get set }
var model: String { get set }
var providerOptions: [String: String] { get set }
}
And in this case Vertex handlers can read what they need:
let projectId = config.providerOptions["gcpProjectId"] ?? ""
let region = config.providerOptions["gcpRegion"] ?? "us-central1"
|
|
||
| HStack { | ||
| Spacer() | ||
| Text("Run `gcloud auth application-default login` to authenticate") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop
2 comments here:
1. Tooltip and placement
I suggest placing this hint somewhere before the button "Import ADC Credentials button" as a tooltip with the (?) icon (see examples in this view below)
2. Different ADC types
The current ADC implementation only supports authorized_user credentials (from gcloud auth application-default login). It would be helpful to also support Service Account keys,
which can be downloaded directly from Google Cloud Console.
Currently in VertexCommon.swift:85-88:
guard credentials.type == "authorized_user" else {
throw APIError.unknown(
"ADC file is not an authorized_user type. Run 'gcloud auth application-default login'"
)
}
Why this matters:
- Service accounts don't require gcloud CLI installation
- Keys are downloadable directly from Cloud Console (IAM → Service Accounts → Keys)
- Preferred for shared machines / enterprise environments
- No interactive browser login needed
Service account JSON structure:
{
"type": "service_account",
"project_id": "...",
"private_key": "-----BEGIN PRIVATE KEY-----\n...",
"client_email": "...",
"token_uri": "https://oauth2.googleapis.com/token"
}
Implementation difference:
- authorized_user: Exchange refresh_token → access_token
- service_account: Sign JWT with private_key → exchange for access_token
| } | ||
|
|
||
| private func updateModelSelection() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@subpop in my tests, models list is not fetched for some reason - please fix this. It seems, that VertexAI can return the models list as described here https://docs.cloud.google.com/vertex-ai/docs/reference/rest/v1/projects.locations.models/list
| <attribute name="type" optional="YES" attributeType="String"/> | ||
| <attribute name="url" optional="YES" attributeType="URI"/> | ||
| <attribute name="useStreamResponse" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/> | ||
| <attribute name="gcpProjectId" optional="YES" attributeType="String"/> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
|
UPD: I've merged feature branch to main, and further changes in Gemini Handler are not expected anymore for a while (if no critical bugs will be found after the release) |


I started working on adding GCP Vertex AI as a provider based off the pre-CloudKit
main. It worked with my setup, but after rebasing to the CloudKit-enabledmain, I can't test anymore (as I don't have a paid Apple Developer account). I'm putting up this PR anyway, since it hopefully still works. I'll see about getting signed up as an Apple Developer account so I can keep testing this, but I figured I should submit the PR anyway.To work with GCP Vertex endpoints, the client needs a "region" and a "project ID" field. I added those as fields in the provider UI. There are a number of ways that GCP requires authentication. I opted for an initial "Select your application_default_credentials.json" file and have the internal client perform the JWT token handshaking. This means that the user has to first run
gcloud auth application-default loginfirst in order to create that file. It's not the most user-friendly approach, but for anyone trying to use GCP Vertex as a provider, I think they should be expecting these kinds of hoops.Coding Assistant Disclosure: I did use Claude to help explore the project and make edits to the files, however I have reviewed the code myself and feel like it's in good "first draft" condition. If there are different approaches you think might work better for your project, I am happy to make additional changes.
One such alternative I have considered is splitting the VertexAIHandler into dedicated GeminiVertexAIHandler and ClaudeVertexAIHandler classes, since they seem to differ so greatly in their API request/response patterns.