diff --git a/package-lock.json b/package-lock.json index 831657f..c6fa805 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,17 +1,18 @@ { "name": "dyad", - "version": "0.19.0", + "version": "0.20.0-beta.1", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "dyad", - "version": "0.19.0", + "version": "0.20.0-beta.1", "license": "MIT", "dependencies": { "@ai-sdk/anthropic": "^2.0.4", "@ai-sdk/azure": "^2.0.17", "@ai-sdk/google": "^2.0.6", + "@ai-sdk/google-vertex": "3.0.16", "@ai-sdk/openai": "2.0.15", "@ai-sdk/openai-compatible": "^1.0.8", "@ai-sdk/provider-utils": "^3.0.3", @@ -129,13 +130,30 @@ } }, "node_modules/@ai-sdk/anthropic": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.4.tgz", - "integrity": "sha512-ii2bZEUPwBitUiK1dpX+HsOarcDGY71G9TVdSJqbfXSVqa+speJNZ8PA/bjuNMml0NyX8VxNsaMg3SwBUCZspA==", + "version": "2.0.9", + "resolved": "https://registry.npmjs.org/@ai-sdk/anthropic/-/anthropic-2.0.9.tgz", + "integrity": "sha512-1kQgL2A3PeqfEcHHmqy4NxRc8rbgLS71bHBuvDFfDz3VAAyndkilPMCLNDSP2mJVGAej2EMWJ1sShRAxzn70jA==", "license": "Apache-2.0", "dependencies": { "@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": { "node": ">=18" @@ -212,13 +230,66 @@ } }, "node_modules/@ai-sdk/google": { - "version": "2.0.6", - "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.6.tgz", - "integrity": "sha512-8acuseWJI+RRH99JDWM/n7IJRuuGNa4YzLXB/leqE/ZByHyIiVWGADjJi/vfnJnmdM5fQnezJ6SRTF6feI5rSQ==", + "version": "2.0.11", + "resolved": "https://registry.npmjs.org/@ai-sdk/google/-/google-2.0.11.tgz", + "integrity": "sha512-dnVIgSz1DZD/0gVau6ifYN3HZFN15HZwC9VjevTFfvrfSfbEvpXj5x/k/zk/0XuQrlQ5g8JiwJtxc9bx24x2xw==", "license": "Apache-2.0", "dependencies": { "@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": { "node": ">=18" @@ -7869,6 +7940,15 @@ "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": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", @@ -8000,6 +8080,12 @@ "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": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -9824,6 +9910,15 @@ "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "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": { "version": "35.1.4", "resolved": "https://registry.npmjs.org/electron/-/electron-35.1.4.tgz", @@ -11123,12 +11218,12 @@ "license": "MIT" }, "node_modules/eventsource-parser": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.3.tgz", - "integrity": "sha512-nVpZkTMM9rF6AQ9gPJpFsNAMt48wIzB5TQgiTLdHiuO8XEDhUgZEhqKlZWXbIzo9VmJ/HvysHqEaVeD5v9TPvA==", + "version": "3.0.6", + "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.6.tgz", + "integrity": "sha512-Vo1ab+QXPzZ4tCa8SwIHJFaSzy4R6SHf7BY79rFBDf0idraZWAkYrDjDj8uWaSm3S2TK+hJ7/t1CEmZ7jXw+pg==", "license": "MIT", "engines": { - "node": ">=20.0.0" + "node": ">=18.0.0" } }, "node_modules/execa": { @@ -11746,6 +11841,83 @@ "license": "MIT", "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": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/geist/-/geist-1.4.2.tgz", @@ -12149,6 +12321,32 @@ "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": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -12200,6 +12398,19 @@ "dev": true, "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": { "version": "17.6.3", "resolved": "https://registry.npmjs.org/happy-dom/-/happy-dom-17.6.3.tgz", @@ -13553,6 +13764,15 @@ "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": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", @@ -13623,6 +13843,27 @@ "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": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", diff --git a/package.json b/package.json index 9361116..998ce18 100644 --- a/package.json +++ b/package.json @@ -88,6 +88,7 @@ "@ai-sdk/anthropic": "^2.0.4", "@ai-sdk/azure": "^2.0.17", "@ai-sdk/google": "^2.0.6", + "@ai-sdk/google-vertex": "3.0.16", "@ai-sdk/openai": "2.0.15", "@ai-sdk/openai-compatible": "^1.0.8", "@ai-sdk/provider-utils": "^3.0.3", diff --git a/src/components/settings/ApiKeyConfiguration.tsx b/src/components/settings/ApiKeyConfiguration.tsx index 3a50894..86e8585 100644 --- a/src/components/settings/ApiKeyConfiguration.tsx +++ b/src/components/settings/ApiKeyConfiguration.tsx @@ -7,6 +7,7 @@ import { AccordionTrigger, } from "@/components/ui/accordion"; import { AzureConfiguration } from "./AzureConfiguration"; +import { VertexConfiguration } from "./VertexConfiguration"; import { Input } from "@/components/ui/input"; import { Button } from "@/components/ui/button"; import { UserSettings } from "@/lib/schemas"; @@ -51,6 +52,10 @@ export function ApiKeyConfiguration({ if (provider === "azure") { return ; } + // Special handling for Google Vertex AI which uses service account credentials + if (provider === "vertex") { + return ; + } const envApiKey = envVarName ? envVars[envVarName] : undefined; const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value; diff --git a/src/components/settings/ProviderSettingsPage.tsx b/src/components/settings/ProviderSettingsPage.tsx index 3240be8..262bcb5 100644 --- a/src/components/settings/ProviderSettingsPage.tsx +++ b/src/components/settings/ProviderSettingsPage.tsx @@ -75,8 +75,20 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) { ? !!(envVars["AZURE_API_KEY"] && envVars["AZURE_RESOURCE_NAME"]) : 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 = - 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 --- const handleSaveKey = async () => { diff --git a/src/components/settings/VertexConfiguration.tsx b/src/components/settings/VertexConfiguration.tsx new file mode 100644 index 0000000..26dfd4e --- /dev/null +++ b/src/components/settings/VertexConfiguration.tsx @@ -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(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 = { + 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 ( +
+
+
+ + setProjectId(e.target.value)} + placeholder="your-gcp-project-id" + /> +
+
+ + setLocation(e.target.value)} + placeholder="us-central1" + /> +

+ 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). +

+
+
+ +