Skip to content

Conversation

@subpop
Copy link

@subpop subpop commented Dec 19, 2025

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-enabled main, 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 login first 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.

@Renset
Copy link
Owner

Renset commented Dec 22, 2025

@subpop, thank you for your contribution, I appreciate it! ✨
I need time to review and test your pull request before I can merge it. Also, I have a few comments and questions that will help us move forward.

  1. Could you explain the advantages of using Vertex AI as the backend for the LLM service? Is it superior to the standard Gemini API, or does it offer useful customizations?
  2. Also, am I correct in understanding that after adding the CloudKit implementation, you cannot build and test macai, or are you only limited to testing iCloud sync?
  3. If possible, please sync your PR with the latest changes in the main branch, which now includes the important "stop inference" functionality. Changes are quite simple; you can look at the changes in GeminiHandler for reference: https://github.com/Renset/macai/pull/139/files#diff-c40ed61e042af4f5421be7bf84e5ab4769e701e5d3d3a32fd1ee35017d849f85

Please let me know if I can provide any help

@subpop
Copy link
Author

subpop commented Dec 24, 2025

  1. Could you explain the advantages of using Vertex AI as the backend for the LLM service? Is it superior to the standard Gemini API, or does it offer useful customizations?

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.

  1. Also, am I correct in understanding that after adding the CloudKit implementation, you cannot build and test macai, or are you only limited to testing iCloud sync?

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.

Cannot create a Mac App Development provisioning profile for "notfullin.com.macai".
Personal development teams, including "Link Dupont", do not support the iCloud capability.
  1. If possible, please sync your PR with the latest changes in the main branch, which now includes the important "stop inference" functionality. Changes are quite simple; you can look at the changes in GeminiHandler for reference: https://github.com/Renset/macai/pull/139/files#diff-c40ed61e042af4f5421be7bf84e5ab4769e701e5d3d3a32fd1ee35017d849f85

I'll take a look and rebase my changes as best I can.

@Renset
Copy link
Owner

Renset commented Dec 29, 2025

@subpop Thank you for the explanation. I think we could add support for VertexAI indeed.
Regarding iCloud, I didn't expect any build issues with the iCloud sync build without paid developer account - so, my bad. I was able to reproduce this, and I pushed the macai-no-icloud.entitlements file to the main branch. This should allow you to build and run macai. To make things work, you have to perform two steps in Xcode:

  1. Set macai-no-icloud.entitlements as the entitlements file instead of the default file.
Screenshot 2025-12-30 at 02 20 21 2. (if not done yet) set the signing settings and blank provisioning profile. Screenshot 2025-12-30 at 02:20:43

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.
@subpop
Copy link
Author

subpop commented Jan 4, 2026

Thanks for that second entitlement file. It worked out great. I'm able to build and run it again. I rebased this branch on main, pulled in the cancellation support, and pushed a second commit that takes a slightly different approach. Instead of an entirely separate VertexAIHandler class, I combined a lot of the common functionality into a GeminiHandlerBase class, and then subclassed it into GeminiHandler for use with the production/Gemini provider, and a GeminiVertexHandler and ClaudVertexHandler when using the Vertex provider. I think this helps to reduce code duplication and reuses existing responses as much as possible.

Copy link
Owner

@Renset Renset left a 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:

  1. See my comments on the lines.
  2. 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.

Copy link
Owner

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",
Copy link
Owner

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
Copy link
Owner

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 }
Copy link
Owner

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")
Copy link
Owner

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() {
Copy link
Owner

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"/>
Copy link
Owner

@Renset Renset Jan 12, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@subpop in the comment above, I suggest using providerOptions instead

@Renset
Copy link
Owner

Renset commented Jan 14, 2026

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)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants