Add Azure OpenAI Custom Model Integration (#1001)

Fixes #710 

This PR implements comprehensive Azure OpenAI integration for Dyad,
enabling users to leverage Azure
OpenAI models through proper environment variable configuration. The
implementation adds Azure as a
supported provider with full integration into the existing language
model architecture, including support
  for GPT-5 models. Key features include environment-based
configuration using `AZURE_API_KEY` and `AZURE_RESOURCE_NAME`,
specialized UI components that provide clear
setup instructions and status indicators, and seamless integration with
Dyad's existing provider system.
The Azure provider leverages the @ai-sdk/azure package (v1.3.25) for
compatibility with the current
  TypeScript language model interfaces.

The implementation includes robust error handling for missing
configuration, comprehensive test coverage
with 9 new unit tests covering critical functionality like model client
creation and error scenarios, and
  an E2E test for the Azure-specific settings UI. 

<img width="1510" height="908" alt="Screenshot 2025-08-18 at 9 14 32 PM"
src="https://github.com/user-attachments/assets/04aa99e1-1590-4bb0-86c9-a67b97bc7500"
/>

---------

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:
Tanner-Maasen
2025-08-30 22:47:25 -05:00
committed by GitHub
parent 86cc50c50c
commit 2ffbbbca8f
14 changed files with 375 additions and 5 deletions

View File

@@ -6,6 +6,7 @@ import {
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { AzureConfiguration } from "./AzureConfiguration";
import { Input } from "@/components/ui/input";
import { Button } from "@/components/ui/button";
import { UserSettings } from "@/lib/schemas";
@@ -46,6 +47,11 @@ export function ApiKeyConfiguration({
onDeleteKey,
isDyad,
}: ApiKeyConfigurationProps) {
// Special handling for Azure OpenAI which requires environment variables
if (provider === "azure") {
return <AzureConfiguration envVars={envVars} />;
}
const envApiKey = envVarName ? envVars[envVarName] : undefined;
const userApiKey = settings?.providerSettings?.[provider]?.apiKey?.value;

View File

@@ -0,0 +1,119 @@
import { Alert, AlertDescription, AlertTitle } from "@/components/ui/alert";
import {
Accordion,
AccordionContent,
AccordionItem,
AccordionTrigger,
} from "@/components/ui/accordion";
import { Info, KeyRound } from "lucide-react";
interface AzureConfigurationProps {
envVars: Record<string, string | undefined>;
}
export function AzureConfiguration({ envVars }: AzureConfigurationProps) {
const azureApiKey = envVars["AZURE_API_KEY"];
const azureResourceName = envVars["AZURE_RESOURCE_NAME"];
const isAzureConfigured = !!(azureApiKey && azureResourceName);
return (
<div className="space-y-4">
<Alert
variant={isAzureConfigured ? "default" : "destructive"}
className={
isAzureConfigured
? ""
: "border-red-200 bg-red-100 dark:border-red-800/50 dark:bg-red-800/20"
}
>
<Info
className={`h-4 w-4 ${isAzureConfigured ? "" : "text-red-800 dark:text-red-400"}`}
/>
<AlertTitle
className={isAzureConfigured ? "" : "text-red-800 dark:text-red-400"}
>
Azure OpenAI Configuration
</AlertTitle>
<AlertDescription
className={isAzureConfigured ? "" : "text-red-800 dark:text-red-400"}
>
Azure OpenAI requires both an API key and resource name to be
configured via environment variables.
</AlertDescription>
</Alert>
<Accordion
type="multiple"
className="w-full space-y-4"
defaultValue={["azure-config"]}
>
<AccordionItem
value="azure-config"
className="border rounded-lg px-4 bg-background"
>
<AccordionTrigger className="text-lg font-medium hover:no-underline cursor-pointer">
Environment Variables Configuration
</AccordionTrigger>
<AccordionContent className="pt-4">
<div className="space-y-4">
<div>
<h4 className="font-medium mb-2">
Required Environment Variables:
</h4>
<div className="space-y-3 text-sm">
<div className="flex justify-between items-center p-3 bg-muted rounded border">
<code className="font-mono text-foreground">
AZURE_API_KEY
</code>
<span
className={`px-2 py-1 rounded text-xs font-medium ${azureApiKey ? "bg-green-100 text-green-800 dark:bg-green-800/20 dark:text-green-400" : "bg-red-100 text-red-800 dark:bg-red-800/20 dark:text-red-400"}`}
>
{azureApiKey ? "Set" : "Not Set"}
</span>
</div>
<div className="flex justify-between items-center p-3 bg-muted rounded border">
<code className="font-mono text-foreground">
AZURE_RESOURCE_NAME
</code>
<span
className={`px-2 py-1 rounded text-xs font-medium ${azureResourceName ? "bg-green-100 text-green-800 dark:bg-green-800/20 dark:text-green-400" : "bg-red-100 text-red-800 dark:bg-red-800/20 dark:text-red-400"}`}
>
{azureResourceName ? "Set" : "Not Set"}
</span>
</div>
</div>
</div>
<div className="mt-4 p-4 bg-blue-50 dark:bg-blue-950/30 rounded border border-blue-200 dark:border-blue-700">
<h5 className="font-medium mb-2 text-blue-900 dark:text-blue-200">
How to configure:
</h5>
<ol className="list-decimal list-inside space-y-1 text-sm text-blue-800 dark:text-blue-300">
<li>Get your API key from the Azure portal</li>
<li>
Find your resource name (the name you gave your Azure OpenAI
resource)
</li>
<li>Set these environment variables before starting Dyad</li>
<li>Restart Dyad after setting the environment variables</li>
</ol>
</div>
{isAzureConfigured && (
<Alert>
<KeyRound className="h-4 w-4" />
<AlertTitle>Azure OpenAI Configured</AlertTitle>
<AlertDescription>
Both required environment variables are set. You can now use
Azure OpenAI models.
</AlertDescription>
</Alert>
)}
</div>
</AccordionContent>
</AccordionItem>
</Accordion>
</div>
);
}

View File

@@ -69,7 +69,14 @@ export function ProviderSettingsPage({ provider }: ProviderSettingsPageProps) {
userApiKey !== "Not Set";
const hasEnvKey = !!(envVarName && envVars[envVarName]);
const isConfigured = isValidUserKey || hasEnvKey; // Configured if either is set
// Special handling for Azure OpenAI configuration
const isAzureConfigured =
provider === "azure"
? !!(envVars["AZURE_API_KEY"] && envVars["AZURE_RESOURCE_NAME"])
: false;
const isConfigured =
provider === "azure" ? isAzureConfigured : isValidUserKey || hasEnvKey; // Configured if either is set
// --- Save Handler ---
const handleSaveKey = async () => {