Add Google Vertex AI provider (#1163)
# Summary
* Adds first-class **Google Vertex AI provider** using
`@ai-sdk/google-vertex`.
* Supports **Gemini 2.5** models and partner **MaaS (Model Garden)**
models via full publisher IDs.
* New **Vertex-specific settings UI** for Project, Location, and Service
Account JSON.
* Implements a **“thinking” toggle** for Gemini 2.5 Flash
* Pro: always on
* Flash: toggleable
* Flash Lite: none
* Fixes *“AI not found”* for Vertex built-ins by mapping to
`publishers/google` paths.
* Hardens **cross-platform file ops** and ensures all tests pass.
---
# What’s New
### Vertex AI Provider
* Uses `@ai-sdk/google-vertex` with `googleAuthOptions.credentials` from
pasted Service Account JSON.
* Configurable **project** and **location**.
* Base URL → `/projects/{project}/locations/{location}`
* Built-ins: `publishers/google/models/<id>`
* Partner MaaS: `publishers/<partner>/models/...`
### Built-in Vertex Models
* `gemini-2.5-pro`
* `gemini-2.5-flash`
* `gemini-2.5-flash-lite`
### Thinking Behavior
* Vertex + Google marked as thinking-capable.
* Pro: always thinking
* Flash: toggle in UI
* Flash Lite: none
### Vertex Settings UI
* New **Google Vertex AI panel** for Project ID, Location, Service
Account JSON.
* Keys encrypted like other secrets.
---
# Fixes
* **Model resolution:** built-ins auto-map to
`publishers/google/models/<id>`.
* **Partner MaaS support:** full publisher IDs work directly (e.g.
DeepSeek).
* **Cross-platform paths:** normalize file ops with `toPosixPath`,
preserve `safeJoin` semantics.
---
# Why This Is Better
* Users can select **Vertex alongside other providers**.
* **More models** available through Model Garden.
* **Dedicated setup UI** reduces misconfig.
* **Thinking toggle** gives control over cost vs. reasoning depth.
---
# Files Changed
* **Provider & Models**: `language_model_helpers.ts`,
`get_model_client.ts`
* **Streaming**: `chat_stream_handlers.ts`
* **Schemas & Encryption**: `schemas.ts`, `settings.ts`
* **Settings UI**: `VertexConfiguration.tsx`, `ApiKeyConfiguration.tsx`
* **Models UI**: `ModelsSection.tsx` (Flash toggle)
* **Setup Detection**: `useLanguageModelProviders.ts`
* **Path Utils**: `path_utils.ts`, `response_processor.ts`
* **Deps**: `package.json` → `@ai-sdk/google-vertex@3.0.16`
---
# Tests & Validation
* **TypeScript**: `npm run ts` → ✅
* **Lint**: `npm run lint` → ✅
* **Unit tests**: `npm test` → ✅ 231 passed, 0 failed
---
# Migration / Notes
* No breaking changes.
* For Vertex usage:
* Ensure Vertex AI API is enabled.
* Service Account needs `roles/aiplatform.user`.
* Region must support model (e.g. `us-central1`).
* Thinking toggle currently affects **only** Gemini 2.5 Flash.
---
# Manual QA
1. Configure Vertex with Project/Location/Service Account JSON.
2. Test built-ins:
* `gemini-2.5-pro`
* `gemini-2.5-flash` (toggle on/off)
* `gemini-2.5-flash-lite`
3. Test MaaS partner model (e.g., DeepSeek) via full publisher ID.
4. Verify other providers remain unaffected.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a first-class Google Vertex AI provider with Gemini 2.5 models, a
Vertex settings panel, and a “thinking” toggle for Gemini 2.5 Flash.
Also fixes model resolution for Vertex and hardens cross-platform file
operations.
- **New Features**
- Vertex AI provider via @ai-sdk/google-vertex with project, location,
and service account JSON.
- Built-in models: gemini-2.5-pro, gemini-2.5-flash,
gemini-2.5-flash-lite.
- “Thinking” support: Pro always on; Flash toggle in Models UI; Flash
Lite none.
- MaaS partners supported via full publisher paths (e.g.,
publishers/<partner>/models/...).
- Vertex settings UI with encrypted service account key storage.
- **Bug Fixes**
- Built-in Vertex models auto-map to publishers/google/models/<id>.
- Consistent file ops across platforms using toPosixPath.
- Vertex readiness detection requires project/location/service account
JSON.
- Streaming “thinking” behavior respects Vertex Flash toggle and Pro
always-on.
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Md Rakibul Islam Rocky <mdrirocky08@gmail.com>
Co-authored-by: graphite-app[bot] <96075541+graphite-app[bot]@users.noreply.github.com>
Co-authored-by: Will Chen <willchen90@gmail.com>
This commit is contained in:
committed by
GitHub
parent
e962964afe
commit
4db6d63b72
@@ -667,28 +667,53 @@ This conversation includes one or more image attachments. When the user uploads
|
||||
} else {
|
||||
logger.log("sending AI request");
|
||||
}
|
||||
// Build provider options with correct Google/Vertex thinking config gating
|
||||
const providerOptions: Record<string, any> = {
|
||||
"dyad-engine": {
|
||||
dyadRequestId,
|
||||
},
|
||||
"dyad-gateway": getExtraProviderOptions(
|
||||
modelClient.builtinProviderId,
|
||||
settings,
|
||||
),
|
||||
openai: {
|
||||
reasoningSummary: "auto",
|
||||
} satisfies OpenAIResponsesProviderOptions,
|
||||
};
|
||||
|
||||
// Conditionally include Google thinking config only for supported models
|
||||
const selectedModelName = settings.selectedModel.name || "";
|
||||
const providerId = modelClient.builtinProviderId;
|
||||
const isVertex = providerId === "vertex";
|
||||
const isGoogle = providerId === "google";
|
||||
const isPartnerModel = selectedModelName.includes("/");
|
||||
const isGeminiModel = selectedModelName.startsWith("gemini");
|
||||
const isFlashLite = selectedModelName.includes("flash-lite");
|
||||
|
||||
// Keep Google provider behavior unchanged: always include includeThoughts
|
||||
if (isGoogle) {
|
||||
providerOptions.google = {
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
},
|
||||
} satisfies GoogleGenerativeAIProviderOptions;
|
||||
}
|
||||
|
||||
// Vertex-specific fix: only enable thinking on supported Gemini models
|
||||
if (isVertex && isGeminiModel && !isFlashLite && !isPartnerModel) {
|
||||
providerOptions.google = {
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
},
|
||||
} satisfies GoogleGenerativeAIProviderOptions;
|
||||
}
|
||||
|
||||
return streamText({
|
||||
maxOutputTokens: await getMaxTokens(settings.selectedModel),
|
||||
temperature: await getTemperature(settings.selectedModel),
|
||||
maxRetries: 2,
|
||||
model: modelClient.model,
|
||||
providerOptions: {
|
||||
"dyad-engine": {
|
||||
dyadRequestId,
|
||||
},
|
||||
"dyad-gateway": getExtraProviderOptions(
|
||||
modelClient.builtinProviderId,
|
||||
settings,
|
||||
),
|
||||
google: {
|
||||
thinkingConfig: {
|
||||
includeThoughts: true,
|
||||
},
|
||||
} satisfies GoogleGenerativeAIProviderOptions,
|
||||
openai: {
|
||||
reasoningSummary: "auto",
|
||||
} satisfies OpenAIResponsesProviderOptions,
|
||||
},
|
||||
providerOptions,
|
||||
system: systemPrompt,
|
||||
messages: chatMessages.filter((m) => m.content),
|
||||
onError: (error: any) => {
|
||||
|
||||
@@ -8,6 +8,7 @@ import { eq } from "drizzle-orm";
|
||||
|
||||
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
|
||||
"google",
|
||||
"vertex",
|
||||
"auto",
|
||||
];
|
||||
|
||||
@@ -144,6 +145,26 @@ export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
||||
dollarSigns: 2,
|
||||
},
|
||||
],
|
||||
vertex: [
|
||||
// Vertex Gemini 2.5 Pro
|
||||
{
|
||||
name: "gemini-2.5-pro",
|
||||
displayName: "Gemini 2.5 Pro",
|
||||
description: "Vertex Gemini 2.5 Pro",
|
||||
maxOutputTokens: 65_536 - 1,
|
||||
contextWindow: 1_048_576,
|
||||
temperature: 0,
|
||||
},
|
||||
// Vertex Gemini 2.5 Flash
|
||||
{
|
||||
name: "gemini-2.5-flash",
|
||||
displayName: "Gemini 2.5 Flash",
|
||||
description: "Vertex Gemini 2.5 Flash",
|
||||
maxOutputTokens: 65_536 - 1,
|
||||
contextWindow: 1_048_576,
|
||||
temperature: 0,
|
||||
},
|
||||
],
|
||||
openrouter: [
|
||||
{
|
||||
name: "qwen/qwen3-coder",
|
||||
@@ -270,6 +291,14 @@ export const CLOUD_PROVIDERS: Record<
|
||||
websiteUrl: "https://aistudio.google.com/app/apikey",
|
||||
gatewayPrefix: "gemini/",
|
||||
},
|
||||
vertex: {
|
||||
displayName: "Google Vertex AI",
|
||||
hasFreeTier: false,
|
||||
websiteUrl: "https://console.cloud.google.com/vertex-ai",
|
||||
// Use the same gateway prefix as Google Gemini for Dyad Pro compatibility.
|
||||
gatewayPrefix: "gemini/",
|
||||
secondary: true,
|
||||
},
|
||||
openrouter: {
|
||||
displayName: "OpenRouter",
|
||||
hasFreeTier: true,
|
||||
|
||||
@@ -1,10 +1,15 @@
|
||||
import { createOpenAI } from "@ai-sdk/openai";
|
||||
import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google";
|
||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||
import { createVertex as createGoogleVertex } from "@ai-sdk/google-vertex";
|
||||
import { azure } from "@ai-sdk/azure";
|
||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
||||
import type { LargeLanguageModel, UserSettings } from "../../lib/schemas";
|
||||
import type {
|
||||
LargeLanguageModel,
|
||||
UserSettings,
|
||||
VertexProviderSetting,
|
||||
} from "../../lib/schemas";
|
||||
import { getEnvVar } from "./read_env";
|
||||
import log from "electron-log";
|
||||
import { getLanguageModelProviders } from "../shared/language_model_helpers";
|
||||
@@ -216,6 +221,45 @@ function getRegularModelClient(
|
||||
backupModelClients: [],
|
||||
};
|
||||
}
|
||||
case "vertex": {
|
||||
// Vertex uses Google service account credentials with project/location
|
||||
const vertexSettings = settings.providerSettings?.[
|
||||
model.provider
|
||||
] as VertexProviderSetting;
|
||||
const project = vertexSettings?.projectId;
|
||||
const location = vertexSettings?.location;
|
||||
const serviceAccountKey = vertexSettings?.serviceAccountKey?.value;
|
||||
|
||||
// Use a baseURL that does NOT pin to publishers/google so that
|
||||
// full publisher model IDs (e.g. publishers/deepseek-ai/models/...) work.
|
||||
const regionHost = `${location === "global" ? "" : `${location}-`}aiplatform.googleapis.com`;
|
||||
const baseURL = `https://${regionHost}/v1/projects/${project}/locations/${location}`;
|
||||
const provider = createGoogleVertex({
|
||||
project,
|
||||
location,
|
||||
baseURL,
|
||||
googleAuthOptions: serviceAccountKey
|
||||
? {
|
||||
// Expecting the user to paste the full JSON of the service account key
|
||||
credentials: JSON.parse(serviceAccountKey),
|
||||
}
|
||||
: undefined,
|
||||
});
|
||||
return {
|
||||
modelClient: {
|
||||
// For built-in Google models on Vertex, the path must include
|
||||
// publishers/google/models/<model>. For partner MaaS models the
|
||||
// full publisher path is already included.
|
||||
model: provider(
|
||||
model.name.includes("/")
|
||||
? model.name
|
||||
: `publishers/google/models/${model.name}`,
|
||||
),
|
||||
builtinProviderId: providerId,
|
||||
},
|
||||
backupModelClients: [],
|
||||
};
|
||||
}
|
||||
case "openrouter": {
|
||||
const provider = createOpenRouter({ apiKey });
|
||||
return {
|
||||
|
||||
Reference in New Issue
Block a user