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

@@ -211,6 +211,40 @@ export const MODEL_OPTIONS: Record<string, ModelOption[]> = {
temperature: 0,
},
],
azure: [
{
name: "gpt-5",
displayName: "GPT-5",
description: "Azure OpenAI GPT-5 model with reasoning capabilities",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-mini",
displayName: "GPT-5 Mini",
description: "Azure OpenAI GPT-5 Mini model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-nano",
displayName: "GPT-5 Nano",
description: "Azure OpenAI GPT-5 Nano model",
maxOutputTokens: 128_000,
contextWindow: 400_000,
temperature: 0,
},
{
name: "gpt-5-chat",
displayName: "GPT-5 Chat",
description: "Azure OpenAI GPT-5 Chat model",
maxOutputTokens: 16_384,
contextWindow: 128_000,
temperature: 0,
},
],
};
export const PROVIDER_TO_ENV_VAR: Record<string, string> = {
@@ -218,6 +252,7 @@ export const PROVIDER_TO_ENV_VAR: Record<string, string> = {
anthropic: "ANTHROPIC_API_KEY",
google: "GEMINI_API_KEY",
openrouter: "OPENROUTER_API_KEY",
azure: "AZURE_API_KEY",
};
export const CLOUD_PROVIDERS: Record<
@@ -258,6 +293,12 @@ export const CLOUD_PROVIDERS: Record<
websiteUrl: "https://academy.dyad.sh/settings",
gatewayPrefix: "dyad/",
},
azure: {
displayName: "Azure OpenAI",
hasFreeTier: false,
websiteUrl: "https://portal.azure.com/",
gatewayPrefix: "",
},
};
const LOCAL_PROVIDERS: Record<

View File

@@ -1,6 +1,7 @@
import { createOpenAI } from "@ai-sdk/openai";
import { createGoogleGenerativeAI as createGoogle } from "@ai-sdk/google";
import { createAnthropic } from "@ai-sdk/anthropic";
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";
@@ -224,6 +225,54 @@ function getRegularModelClient(
backupModelClients: [],
};
}
case "azure": {
// Check if we're in e2e testing mode
const testAzureBaseUrl = getEnvVar("TEST_AZURE_BASE_URL");
if (testAzureBaseUrl) {
// Use fake server for e2e testing
logger.info(`Using test Azure base URL: ${testAzureBaseUrl}`);
const provider = createOpenAICompatible({
name: "azure-test",
baseURL: testAzureBaseUrl,
apiKey: "fake-api-key-for-testing",
});
return {
modelClient: {
model: provider(model.name),
builtinProviderId: providerId,
},
backupModelClients: [],
};
}
// Azure OpenAI requires both API key and resource name as env vars
// We use environment variables for Azure configuration
const resourceName = getEnvVar("AZURE_RESOURCE_NAME");
const azureApiKey = getEnvVar("AZURE_API_KEY");
if (!resourceName) {
throw new Error(
"Azure OpenAI resource name is required. Please set the AZURE_RESOURCE_NAME environment variable.",
);
}
if (!azureApiKey) {
throw new Error(
"Azure OpenAI API key is required. Please set the AZURE_API_KEY environment variable.",
);
}
// Use the default Azure provider with environment variables
// The azure provider automatically picks up AZURE_RESOURCE_NAME and AZURE_API_KEY
return {
modelClient: {
model: azure(model.name),
builtinProviderId: providerId,
},
backupModelClients: [],
};
}
case "ollama": {
const provider = createOllamaProvider({ baseURL: getOllamaApiUrl() });
return {