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
269
package-lock.json
generated
269
package-lock.json
generated
@@ -1,17 +1,18 @@
|
|||||||
{
|
{
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.19.0",
|
"version": "0.20.0-beta.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "dyad",
|
"name": "dyad",
|
||||||
"version": "0.19.0",
|
"version": "0.20.0-beta.1",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/anthropic": "^2.0.4",
|
"@ai-sdk/anthropic": "^2.0.4",
|
||||||
"@ai-sdk/azure": "^2.0.17",
|
"@ai-sdk/azure": "^2.0.17",
|
||||||
"@ai-sdk/google": "^2.0.6",
|
"@ai-sdk/google": "^2.0.6",
|
||||||
|
"@ai-sdk/google-vertex": "3.0.16",
|
||||||
"@ai-sdk/openai": "2.0.15",
|
"@ai-sdk/openai": "2.0.15",
|
||||||
"@ai-sdk/openai-compatible": "^1.0.8",
|
"@ai-sdk/openai-compatible": "^1.0.8",
|
||||||
"@ai-sdk/provider-utils": "^3.0.3",
|
"@ai-sdk/provider-utils": "^3.0.3",
|
||||||
@@ -129,13 +130,30 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/anthropic": {
|
"node_modules/@ai-sdk/anthropic": {
|
||||||
"version": "2.0.4",
|
"version": "2.0.9",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.9.tgz",
|
||||||
"integrity": "sha512-ii2bZEUPwBitUiK1dpX+HsOarcDGY71G9TVdSJqbfXSVqa+speJNZ8PA/bjuNMml0NyX8VxNsaMg3SwBUCZspA==",
|
"integrity": "sha512-1kQgL2A3PeqfEcHHmqy4NxRc8rbgLS71bHBuvDFfDz3VAAyndkilPMCLNDSP2mJVGAej2EMWJ1sShRAxzn70jA==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.3"
|
"@ai-sdk/provider-utils": "3.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/anthropic/node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"eventsource-parser": "^3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -212,13 +230,66 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@ai-sdk/google": {
|
"node_modules/@ai-sdk/google": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.11",
|
||||||
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.11.tgz",
|
||||||
"integrity": "sha512-8acuseWJI+RRH99JDWM/n7IJRuuGNa4YzLXB/leqE/ZByHyIiVWGADjJi/vfnJnmdM5fQnezJ6SRTF6feI5rSQ==",
|
"integrity": "sha512-dnVIgSz1DZD/0gVau6ifYN3HZFN15HZwC9VjevTFfvrfSfbEvpXj5x/k/zk/0XuQrlQ5g8JiwJtxc9bx24x2xw==",
|
||||||
"license": "Apache-2.0",
|
"license": "Apache-2.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@ai-sdk/provider": "2.0.0",
|
"@ai-sdk/provider": "2.0.0",
|
||||||
"@ai-sdk/provider-utils": "3.0.3"
|
"@ai-sdk/provider-utils": "3.0.7"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/google-vertex": {
|
||||||
|
"version": "3.0.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/google-vertex/-/google-vertex-3.0.16.tgz",
|
||||||
|
"integrity": "sha512-tStlnOCRGRqKKJSCOtXhijX4r9kYVK2v+Vs7miJnfvr3sZfO8nRS0xnNhfgu17xuNi5LMMufeCYURTz4lKxzUQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/anthropic": "2.0.9",
|
||||||
|
"@ai-sdk/google": "2.0.11",
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@ai-sdk/provider-utils": "3.0.7",
|
||||||
|
"google-auth-library": "^9.15.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/google-vertex/node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"eventsource-parser": "^3.0.5"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=18"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"zod": "^3.25.76 || ^4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@ai-sdk/google/node_modules/@ai-sdk/provider-utils": {
|
||||||
|
"version": "3.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/@ai-sdk/provider-utils/-/provider-utils-3.0.7.tgz",
|
||||||
|
"integrity": "sha512-o3BS5/t8KnBL3ubP8k3w77AByOypLm+pkIL/DCw0qKkhDbvhCy+L3hRTGPikpdb8WHcylAeKsjgwOxhj4cqTUA==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@ai-sdk/provider": "2.0.0",
|
||||||
|
"@standard-schema/spec": "^1.0.0",
|
||||||
|
"eventsource-parser": "^3.0.5"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=18"
|
"node": ">=18"
|
||||||
@@ -7869,6 +7940,15 @@
|
|||||||
"prebuild-install": "^7.1.1"
|
"prebuild-install": "^7.1.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/bignumber.js": {
|
||||||
|
"version": "9.3.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz",
|
||||||
|
"integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/bindings": {
|
"node_modules/bindings": {
|
||||||
"version": "1.5.0",
|
"version": "1.5.0",
|
||||||
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
"resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz",
|
||||||
@@ -8000,6 +8080,12 @@
|
|||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/buffer-equal-constant-time": {
|
||||||
|
"version": "1.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz",
|
||||||
|
"integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==",
|
||||||
|
"license": "BSD-3-Clause"
|
||||||
|
},
|
||||||
"node_modules/buffer-from": {
|
"node_modules/buffer-from": {
|
||||||
"version": "1.1.2",
|
"version": "1.1.2",
|
||||||
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
|
||||||
@@ -9824,6 +9910,15 @@
|
|||||||
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/ecdsa-sig-formatter": {
|
||||||
|
"version": "1.0.11",
|
||||||
|
"resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz",
|
||||||
|
"integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/electron": {
|
"node_modules/electron": {
|
||||||
"version": "35.1.4",
|
"version": "35.1.4",
|
||||||
"resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz",
|
"resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz",
|
||||||
@@ -11123,12 +11218,12 @@
|
|||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
"node_modules/eventsource-parser": {
|
"node_modules/eventsource-parser": {
|
||||||
"version": "3.0.3",
|
"version": "3.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz",
|
||||||
"integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==",
|
"integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=20.0.0"
|
"node": ">=18.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
@@ -11746,6 +11841,83 @@
|
|||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true
|
||||||
},
|
},
|
||||||
|
"node_modules/gaxios": {
|
||||||
|
"version": "6.7.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gaxios/-/gaxios-6.7.1.tgz",
|
||||||
|
"integrity": "sha512-LDODD4TMYx7XXdpwxAVRAIAuB0bzv0s+ywFonY46k126qzQHT9ygyoa9tncmOiQmmDrik65UYsEkv3lbfqQ3yQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"extend": "^3.0.2",
|
||||||
|
"https-proxy-agent": "^7.0.1",
|
||||||
|
"is-stream": "^2.0.0",
|
||||||
|
"node-fetch": "^2.6.9",
|
||||||
|
"uuid": "^9.0.1"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gaxios/node_modules/agent-base": {
|
||||||
|
"version": "7.1.4",
|
||||||
|
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz",
|
||||||
|
"integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gaxios/node_modules/https-proxy-agent": {
|
||||||
|
"version": "7.0.6",
|
||||||
|
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz",
|
||||||
|
"integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"agent-base": "^7.1.2",
|
||||||
|
"debug": "4"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">= 14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gaxios/node_modules/is-stream": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gaxios/node_modules/uuid": {
|
||||||
|
"version": "9.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz",
|
||||||
|
"integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==",
|
||||||
|
"funding": [
|
||||||
|
"https://github.com/sponsors/broofa",
|
||||||
|
"https://github.com/sponsors/ctavan"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"uuid": "dist/bin/uuid"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/gcp-metadata": {
|
||||||
|
"version": "6.1.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/gcp-metadata/-/gcp-metadata-6.1.1.tgz",
|
||||||
|
"integrity": "sha512-a4tiq7E0/5fTjxPAaH4jpjkSv/uCaU2p5KC6HVGrvl0cDjA8iBZv4vv1gyzlmK0ZUKqwpOyQMKzZQe3lTit77A==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"gaxios": "^6.1.1",
|
||||||
|
"google-logging-utils": "^0.0.2",
|
||||||
|
"json-bigint": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/geist": {
|
"node_modules/geist": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/geist/-/geist-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/geist/-/geist-1.4.2.tgz",
|
||||||
@@ -12149,6 +12321,32 @@
|
|||||||
"url": "https://github.com/sponsors/sindresorhus"
|
"url": "https://github.com/sponsors/sindresorhus"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/google-auth-library": {
|
||||||
|
"version": "9.15.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/google-auth-library/-/google-auth-library-9.15.1.tgz",
|
||||||
|
"integrity": "sha512-Jb6Z0+nvECVz+2lzSMt9u98UsoakXxA2HGHMCxh+so3n90XgYWkq5dur19JAJV7ONiJY22yBTyJB1TSkvPq9Ng==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"dependencies": {
|
||||||
|
"base64-js": "^1.3.0",
|
||||||
|
"ecdsa-sig-formatter": "^1.0.11",
|
||||||
|
"gaxios": "^6.1.1",
|
||||||
|
"gcp-metadata": "^6.1.0",
|
||||||
|
"gtoken": "^7.0.0",
|
||||||
|
"jws": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/google-logging-utils": {
|
||||||
|
"version": "0.0.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/google-logging-utils/-/google-logging-utils-0.0.2.tgz",
|
||||||
|
"integrity": "sha512-NEgUnEcBiP5HrPzufUkBzJOD/Sxsco3rLNo1F1TNf7ieU8ryUzBhqba8r756CjLX7rn3fHl6iLEwPYuqpoKgQQ==",
|
||||||
|
"license": "Apache-2.0",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/gopd": {
|
"node_modules/gopd": {
|
||||||
"version": "1.2.0",
|
"version": "1.2.0",
|
||||||
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
"resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz",
|
||||||
@@ -12200,6 +12398,19 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT"
|
"license": "MIT"
|
||||||
},
|
},
|
||||||
|
"node_modules/gtoken": {
|
||||||
|
"version": "7.1.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/gtoken/-/gtoken-7.1.0.tgz",
|
||||||
|
"integrity": "sha512-pCcEwRi+TKpMlxAQObHDQ56KawURgyAf6jtIY046fJ5tIv3zDe/LEIubckAO8fj6JnAxLdmWkUfNyulQ2iKdEw==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"gaxios": "^6.0.0",
|
||||||
|
"jws": "^4.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/happy-dom": {
|
"node_modules/happy-dom": {
|
||||||
"version": "17.6.3",
|
"version": "17.6.3",
|
||||||
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz",
|
"resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz",
|
||||||
@@ -13553,6 +13764,15 @@
|
|||||||
"node": ">=6"
|
"node": ">=6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/json-bigint": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/json-bigint/-/json-bigint-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"bignumber.js": "^9.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/json-buffer": {
|
"node_modules/json-buffer": {
|
||||||
"version": "3.0.1",
|
"version": "3.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
|
||||||
@@ -13623,6 +13843,27 @@
|
|||||||
"node": ">=8"
|
"node": ">=8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/jwa": {
|
||||||
|
"version": "2.0.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz",
|
||||||
|
"integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"buffer-equal-constant-time": "^1.0.1",
|
||||||
|
"ecdsa-sig-formatter": "1.0.11",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/jws": {
|
||||||
|
"version": "4.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/jws/-/jws-4.0.0.tgz",
|
||||||
|
"integrity": "sha512-KDncfTmOZoOMTFG4mBlG0qUIOlc03fmzH+ru6RgYVZhPkyiy/92Owlt/8UEN+a4TXR1FQetfIpJE8ApdvdVxTg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"jwa": "^2.0.0",
|
||||||
|
"safe-buffer": "^5.0.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/keyv": {
|
"node_modules/keyv": {
|
||||||
"version": "4.5.4",
|
"version": "4.5.4",
|
||||||
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
"resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
|
||||||
|
|||||||
@@ -88,6 +88,7 @@
|
|||||||
"@ai-sdk/anthropic": "^2.0.4",
|
"@ai-sdk/anthropic": "^2.0.4",
|
||||||
"@ai-sdk/azure": "^2.0.17",
|
"@ai-sdk/azure": "^2.0.17",
|
||||||
"@ai-sdk/google": "^2.0.6",
|
"@ai-sdk/google": "^2.0.6",
|
||||||
|
"@ai-sdk/google-vertex": "3.0.16",
|
||||||
"@ai-sdk/openai": "2.0.15",
|
"@ai-sdk/openai": "2.0.15",
|
||||||
"@ai-sdk/openai-compatible": "^1.0.8",
|
"@ai-sdk/openai-compatible": "^1.0.8",
|
||||||
"@ai-sdk/provider-utils": "^3.0.3",
|
"@ai-sdk/provider-utils": "^3.0.3",
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import {
|
|||||||
AccordionTrigger,
|
AccordionTrigger,
|
||||||
} from "@/components/ui/accordion";
|
} from "@/components/ui/accordion";
|
||||||
import { AzureConfiguration } from "./AzureConfiguration";
|
import { AzureConfiguration } from "./AzureConfiguration";
|
||||||
|
import { VertexConfiguration } from "./VertexConfiguration";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { UserSettings } from "@/lib/schemas";
|
import { UserSettings } from "@/lib/schemas";
|
||||||
@@ -51,6 +52,10 @@ export function ApiKeyConfiguration({
|
|||||||
if (provider === "azure") {
|
if (provider === "azure") {
|
||||||
return <AzureConfiguration envVars={envVars} />;
|
return <AzureConfiguration envVars={envVars} />;
|
||||||
}
|
}
|
||||||
|
// Special handling for Google Vertex AI which uses service account credentials
|
||||||
|
if (provider === "vertex") {
|
||||||
|
return <VertexConfiguration />;
|
||||||
|
}
|
||||||
|
|
||||||
const envApiKey = envVarName ? envVars[envVarName] : undefined;
|
const envApiKey = envVarName ? envVars[envVarName] : undefined;
|
||||||
const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value;
|
const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value;
|
||||||
|
|||||||
@@ -75,8 +75,20 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
|
|||||||
? !!(envVars["AZURE_API_KEY"] && envVars["AZURE_RESOURCE_NAME"])
|
? !!(envVars["AZURE_API_KEY"] && envVars["AZURE_RESOURCE_NAME"])
|
||||||
: false;
|
: false;
|
||||||
|
|
||||||
|
// Special handling for Vertex configuration status
|
||||||
|
const vertexSettings = settings?.providerSettings?.vertex as any;
|
||||||
|
const isVertexConfigured = Boolean(
|
||||||
|
vertexSettings?.projectId &&
|
||||||
|
vertexSettings?.location &&
|
||||||
|
vertexSettings?.serviceAccountKey?.value,
|
||||||
|
);
|
||||||
|
|
||||||
const isConfigured =
|
const isConfigured =
|
||||||
provider === "azure" ? isAzureConfigured : isValidUserKey || hasEnvKey; // Configured if either is set
|
provider === "azure"
|
||||||
|
? isAzureConfigured
|
||||||
|
: provider === "vertex"
|
||||||
|
? isVertexConfigured
|
||||||
|
: isValidUserKey || hasEnvKey; // Configured if either is set
|
||||||
|
|
||||||
// --- Save Handler ---
|
// --- Save Handler ---
|
||||||
const handleSaveKey = async () => {
|
const handleSaveKey = async () => {
|
||||||
|
|||||||
141
src/components/settings/VertexConfiguration.tsx
Normal file
141
src/components/settings/VertexConfiguration.tsx
Normal file
@@ -0,0 +1,141 @@
|
|||||||
|
import { useState, useEffect } from "react";
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Textarea } from "@/components/ui/textarea";
|
||||||
|
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
|
||||||
|
import { Info, CheckCircle2 } from "lucide-react";
|
||||||
|
import { useSettings } from "@/hooks/useSettings";
|
||||||
|
import type { UserSettings, VertexProviderSetting } from "@/lib/schemas";
|
||||||
|
|
||||||
|
export function VertexConfiguration() {
|
||||||
|
const { settings, updateSettings } = useSettings();
|
||||||
|
const existing =
|
||||||
|
(settings?.providerSettings?.vertex as VertexProviderSetting) ?? {};
|
||||||
|
|
||||||
|
const [projectId, setProjectId] = useState(existing.projectId || "");
|
||||||
|
const [location, setLocation] = useState(existing.location || "");
|
||||||
|
const [serviceAccountKey, setServiceAccountKey] = useState(
|
||||||
|
existing.serviceAccountKey?.value || "",
|
||||||
|
);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(null);
|
||||||
|
const [saved, setSaved] = useState(false);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setProjectId(existing.projectId || "");
|
||||||
|
setLocation(existing.location || "");
|
||||||
|
setServiceAccountKey(existing.serviceAccountKey?.value || "");
|
||||||
|
}, [settings?.providerSettings?.vertex]);
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
setError(null);
|
||||||
|
setSaved(false);
|
||||||
|
try {
|
||||||
|
// If provided, ensure the service account JSON parses
|
||||||
|
if (serviceAccountKey) {
|
||||||
|
JSON.parse(serviceAccountKey);
|
||||||
|
}
|
||||||
|
} catch (e: any) {
|
||||||
|
setError("Service account JSON is invalid: " + e.message);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setSaving(true);
|
||||||
|
try {
|
||||||
|
const settingsUpdate: Partial<UserSettings> = {
|
||||||
|
providerSettings: {
|
||||||
|
...settings?.providerSettings,
|
||||||
|
vertex: {
|
||||||
|
...existing,
|
||||||
|
projectId: projectId.trim() || undefined,
|
||||||
|
location: location || undefined,
|
||||||
|
serviceAccountKey: serviceAccountKey
|
||||||
|
? { value: serviceAccountKey }
|
||||||
|
: undefined,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
await updateSettings(settingsUpdate);
|
||||||
|
setSaved(true);
|
||||||
|
} catch (e: any) {
|
||||||
|
setError(e?.message || "Failed to save Vertex settings");
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isConfigured = Boolean(
|
||||||
|
(projectId.trim() && location && serviceAccountKey) ||
|
||||||
|
(existing.projectId &&
|
||||||
|
existing.location &&
|
||||||
|
existing.serviceAccountKey?.value),
|
||||||
|
);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-4">
|
||||||
|
<div className="grid grid-cols-1 gap-4">
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">Project ID</label>
|
||||||
|
<Input
|
||||||
|
value={projectId}
|
||||||
|
onChange={(e) => setProjectId(e.target.value)}
|
||||||
|
placeholder="your-gcp-project-id"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">Location</label>
|
||||||
|
<Input
|
||||||
|
value={location}
|
||||||
|
onChange={(e) => setLocation(e.target.value)}
|
||||||
|
placeholder="us-central1"
|
||||||
|
/>
|
||||||
|
<p className="mt-1 text-xs text-muted-foreground">
|
||||||
|
If you see a "model not found" error, try a different region. Some
|
||||||
|
partner models (MaaS) are only available in specific locations
|
||||||
|
(e.g., us-central1, us-west2).
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-medium mb-1">
|
||||||
|
Service Account JSON Key
|
||||||
|
</label>
|
||||||
|
<Textarea
|
||||||
|
value={serviceAccountKey}
|
||||||
|
onChange={(e) => setServiceAccountKey(e.target.value)}
|
||||||
|
placeholder="Paste the full JSON contents of your service account key here"
|
||||||
|
className="min-h-40"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Button onClick={onSave} disabled={saving}>
|
||||||
|
{saving ? "Saving..." : "Save Settings"}
|
||||||
|
</Button>
|
||||||
|
{saved && !error && (
|
||||||
|
<span className="flex items-center text-green-600 text-sm">
|
||||||
|
<CheckCircle2 className="h-4 w-4 mr-1" /> Saved
|
||||||
|
</span>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{!isConfigured && (
|
||||||
|
<Alert variant="default">
|
||||||
|
<Info className="h-4 w-4" />
|
||||||
|
<AlertTitle>Configuration Required</AlertTitle>
|
||||||
|
<AlertDescription>
|
||||||
|
Provide Project, Location, and a service account JSON key with
|
||||||
|
Vertex AI access.
|
||||||
|
</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{error && (
|
||||||
|
<Alert variant="destructive">
|
||||||
|
<AlertTitle>Save Error</AlertTitle>
|
||||||
|
<AlertDescription>{error}</AlertDescription>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@ import { useQuery } from "@tanstack/react-query";
|
|||||||
import { IpcClient } from "@/ipc/ipc_client";
|
import { IpcClient } from "@/ipc/ipc_client";
|
||||||
import type { LanguageModelProvider } from "@/ipc/ipc_types";
|
import type { LanguageModelProvider } from "@/ipc/ipc_types";
|
||||||
import { useSettings } from "./useSettings";
|
import { useSettings } from "./useSettings";
|
||||||
import { cloudProviders } from "@/lib/schemas";
|
import { cloudProviders, VertexProviderSetting } from "@/lib/schemas";
|
||||||
|
|
||||||
export function useLanguageModelProviders() {
|
export function useLanguageModelProviders() {
|
||||||
const ipcClient = IpcClient.getInstance();
|
const ipcClient = IpcClient.getInstance();
|
||||||
@@ -20,6 +20,18 @@ export function useLanguageModelProviders() {
|
|||||||
if (queryResult.isLoading) {
|
if (queryResult.isLoading) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
// Vertex uses service account credentials instead of an API key
|
||||||
|
if (provider === "vertex") {
|
||||||
|
const vertexSettings = providerSettings as VertexProviderSetting;
|
||||||
|
if (
|
||||||
|
vertexSettings?.serviceAccountKey?.value &&
|
||||||
|
vertexSettings?.projectId &&
|
||||||
|
vertexSettings?.location
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
if (providerSettings?.apiKey?.value) {
|
if (providerSettings?.apiKey?.value) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -667,28 +667,53 @@ This conversation includes one or more image attachments. When the user uploads
|
|||||||
} else {
|
} else {
|
||||||
logger.log("sending AI request");
|
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({
|
return streamText({
|
||||||
maxOutputTokens: await getMaxTokens(settings.selectedModel),
|
maxOutputTokens: await getMaxTokens(settings.selectedModel),
|
||||||
temperature: await getTemperature(settings.selectedModel),
|
temperature: await getTemperature(settings.selectedModel),
|
||||||
maxRetries: 2,
|
maxRetries: 2,
|
||||||
model: modelClient.model,
|
model: modelClient.model,
|
||||||
providerOptions: {
|
providerOptions,
|
||||||
"dyad-engine": {
|
|
||||||
dyadRequestId,
|
|
||||||
},
|
|
||||||
"dyad-gateway": getExtraProviderOptions(
|
|
||||||
modelClient.builtinProviderId,
|
|
||||||
settings,
|
|
||||||
),
|
|
||||||
google: {
|
|
||||||
thinkingConfig: {
|
|
||||||
includeThoughts: true,
|
|
||||||
},
|
|
||||||
} satisfies GoogleGenerativeAIProviderOptions,
|
|
||||||
openai: {
|
|
||||||
reasoningSummary: "auto",
|
|
||||||
} satisfies OpenAIResponsesProviderOptions,
|
|
||||||
},
|
|
||||||
system: systemPrompt,
|
system: systemPrompt,
|
||||||
messages: chatMessages.filter((m) => m.content),
|
messages: chatMessages.filter((m) => m.content),
|
||||||
onError: (error: any) => {
|
onError: (error: any) => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import { eq } from "drizzle-orm";
|
|||||||
|
|
||||||
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
|
export const PROVIDERS_THAT_SUPPORT_THINKING: (keyof typeof MODEL_OPTIONS)[] = [
|
||||||
"google",
|
"google",
|
||||||
|
"vertex",
|
||||||
"auto",
|
"auto",
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -144,6 +145,26 @@ export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
|
|||||||
dollarSigns: 2,
|
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: [
|
openrouter: [
|
||||||
{
|
{
|
||||||
name: "qwen/qwen3-coder",
|
name: "qwen/qwen3-coder",
|
||||||
@@ -270,6 +291,14 @@ export const CLOUD_PROVIDERS: Record<
|
|||||||
websiteUrl: "https://aistudio.google.com/app/apikey",
|
websiteUrl: "https://aistudio.google.com/app/apikey",
|
||||||
gatewayPrefix: "gemini/",
|
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: {
|
openrouter: {
|
||||||
displayName: "OpenRouter",
|
displayName: "OpenRouter",
|
||||||
hasFreeTier: true,
|
hasFreeTier: true,
|
||||||
|
|||||||
@@ -1,10 +1,15 @@
|
|||||||
import { createOpenAI } from "@ai-sdk/openai";
|
import { createOpenAI } from "@ai-sdk/openai";
|
||||||
import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google";
|
import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google";
|
||||||
import { createAnthropic } from "@ai-sdk/anthropic";
|
import { createAnthropic } from "@ai-sdk/anthropic";
|
||||||
|
import { createVertex as createGoogleVertex } from "@ai-sdk/google-vertex";
|
||||||
import { azure } from "@ai-sdk/azure";
|
import { azure } from "@ai-sdk/azure";
|
||||||
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
import { createOpenRouter } from "@openrouter/ai-sdk-provider";
|
||||||
import { createOpenAICompatible } from "@ai-sdk/openai-compatible";
|
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 { getEnvVar } from "./read_env";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
import { getLanguageModelProviders } from "../shared/language_model_helpers";
|
import { getLanguageModelProviders } from "../shared/language_model_helpers";
|
||||||
@@ -216,6 +221,45 @@ function getRegularModelClient(
|
|||||||
backupModelClients: [],
|
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": {
|
case "openrouter": {
|
||||||
const provider = createOpenRouter({ apiKey });
|
const provider = createOpenRouter({ apiKey });
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ const providers = [
|
|||||||
"openai",
|
"openai",
|
||||||
"anthropic",
|
"anthropic",
|
||||||
"google",
|
"google",
|
||||||
|
"vertex",
|
||||||
"auto",
|
"auto",
|
||||||
"openrouter",
|
"openrouter",
|
||||||
"ollama",
|
"ollama",
|
||||||
@@ -57,15 +58,35 @@ export type LargeLanguageModel = z.infer<typeof LargeLanguageModelSchema>;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Zod schema for provider settings
|
* Zod schema for provider settings
|
||||||
|
* Regular providers use only apiKey. Vertex has additional optional fields.
|
||||||
*/
|
*/
|
||||||
export const ProviderSettingSchema = z.object({
|
export const RegularProviderSettingSchema = z.object({
|
||||||
apiKey: SecretSchema.optional(),
|
apiKey: SecretSchema.optional(),
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export const VertexProviderSettingSchema = z.object({
|
||||||
|
// We make this undefined so that it makes existing callsites easier.
|
||||||
|
apiKey: z.undefined(),
|
||||||
|
projectId: z.string().optional(),
|
||||||
|
location: z.string().optional(),
|
||||||
|
serviceAccountKey: SecretSchema.optional(),
|
||||||
|
});
|
||||||
|
|
||||||
|
export const ProviderSettingSchema = z.union([
|
||||||
|
// Must use more specific type first!
|
||||||
|
// Zod uses the first type that matches.
|
||||||
|
VertexProviderSettingSchema,
|
||||||
|
RegularProviderSettingSchema,
|
||||||
|
]);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Type derived from the ProviderSettingSchema
|
* Type derived from the ProviderSettingSchema
|
||||||
*/
|
*/
|
||||||
export type ProviderSetting = z.infer<typeof ProviderSettingSchema>;
|
export type ProviderSetting = z.infer<typeof ProviderSettingSchema>;
|
||||||
|
export type RegularProviderSetting = z.infer<
|
||||||
|
typeof RegularProviderSettingSchema
|
||||||
|
>;
|
||||||
|
export type VertexProviderSetting = z.infer<typeof VertexProviderSettingSchema>;
|
||||||
|
|
||||||
export const RuntimeModeSchema = z.enum(["web-sandbox", "local-node", "unset"]);
|
export const RuntimeModeSchema = z.enum(["web-sandbox", "local-node", "unset"]);
|
||||||
export type RuntimeMode = z.infer<typeof RuntimeModeSchema>;
|
export type RuntimeMode = z.infer<typeof RuntimeModeSchema>;
|
||||||
|
|||||||
@@ -1,7 +1,12 @@
|
|||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
import { getUserDataPath } from "../paths/paths";
|
import { getUserDataPath } from "../paths/paths";
|
||||||
import { UserSettingsSchema, type UserSettings, Secret } from "../lib/schemas";
|
import {
|
||||||
|
UserSettingsSchema,
|
||||||
|
type UserSettings,
|
||||||
|
Secret,
|
||||||
|
VertexProviderSetting,
|
||||||
|
} from "../lib/schemas";
|
||||||
import { safeStorage } from "electron";
|
import { safeStorage } from "electron";
|
||||||
import { v4 as uuidv4 } from "uuid";
|
import { v4 as uuidv4 } from "uuid";
|
||||||
import log from "electron-log";
|
import log from "electron-log";
|
||||||
@@ -114,6 +119,17 @@ export function readSettings(): UserSettings {
|
|||||||
encryptionType,
|
encryptionType,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
// Decrypt Vertex service account key if present
|
||||||
|
const v = combinedSettings.providerSettings[
|
||||||
|
provider
|
||||||
|
] as VertexProviderSetting;
|
||||||
|
if (provider === "vertex" && v?.serviceAccountKey) {
|
||||||
|
const encryptionType = v.serviceAccountKey.encryptionType;
|
||||||
|
v.serviceAccountKey = {
|
||||||
|
value: decrypt(v.serviceAccountKey),
|
||||||
|
encryptionType,
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Validate and merge with defaults
|
// Validate and merge with defaults
|
||||||
@@ -171,6 +187,11 @@ export function writeSettings(settings: Partial<UserSettings>): void {
|
|||||||
newSettings.providerSettings[provider].apiKey.value,
|
newSettings.providerSettings[provider].apiKey.value,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
// Encrypt Vertex service account key if present
|
||||||
|
const v = newSettings.providerSettings[provider] as VertexProviderSetting;
|
||||||
|
if (provider === "vertex" && v?.serviceAccountKey) {
|
||||||
|
v.serviceAccountKey = encrypt(v.serviceAccountKey.value);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
const validatedSettings = UserSettingsSchema.parse(newSettings);
|
const validatedSettings = UserSettingsSchema.parse(newSettings);
|
||||||
fs.writeFileSync(filePath, JSON.stringify(validatedSettings, null, 2));
|
fs.writeFileSync(filePath, JSON.stringify(validatedSettings, null, 2));
|
||||||
|
|||||||
Reference in New Issue
Block a user