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

@@ -0,0 +1,43 @@
import { expect } from "@playwright/test";
import { test as testWithPo } from "./helpers/test_helper";
testWithPo("Azure provider settings UI", async ({ po }) => {
await po.setUp();
await po.goToSettingsTab();
// Look for Azure OpenAI in the provider list
await expect(po.page.getByText("Azure OpenAI")).toBeVisible();
// Navigate to Azure provider settings
await po.page.getByText("Azure OpenAI").click();
// Wait for Azure settings page to load
await po.page.waitForSelector('h1:has-text("Configure Azure OpenAI")', {
state: "visible",
timeout: 5000,
});
// Check that Azure-specific UI is displayed
await expect(po.page.getByText("Azure OpenAI Configuration")).toBeVisible();
await expect(po.page.getByText("AZURE_API_KEY")).toBeVisible();
await expect(po.page.getByText("AZURE_RESOURCE_NAME")).toBeVisible();
// Check environment variable status indicators exist
await expect(
po.page.getByText("Environment Variables Configuration"),
).toBeVisible();
// Check setup instructions are present
await expect(po.page.getByText("How to configure:")).toBeVisible();
await expect(
po.page.getByText("Get your API key from the Azure portal"),
).toBeVisible();
await expect(po.page.getByText("Find your resource name")).toBeVisible();
await expect(
po.page.getByText("Set these environment variables before starting Dyad"),
).toBeVisible();
// Check that status indicators show "Not Set" (since no env vars are configured in test)
const statusElements = po.page.locator(".bg-red-100, .bg-red-800\\/20");
await expect(statusElements.first()).toBeVisible();
});

View File

@@ -0,0 +1,20 @@
import { testSkipIfWindows } from "./helpers/test_helper";
// Set environment variables before the test runs to enable Azure testing
process.env.TEST_AZURE_BASE_URL = "http://localhost:3500/azure";
process.env.AZURE_API_KEY = "fake-azure-key-for-testing";
process.env.AZURE_RESOURCE_NAME = "fake-resource-for-testing";
testSkipIfWindows("send message through Azure OpenAI", async ({ po }) => {
// Set up Azure without test provider
await po.setUpAzure();
// Select Azure model
await po.selectTestAzureModel();
// Send a test prompt that returns a normal conversational response
await po.sendPrompt("tc=basic");
// Verify we get a response (this means Azure integration is working)
await po.snapshotMessages();
});

View File

@@ -0,0 +1 @@
This is a simple basic response

View File

@@ -673,7 +673,7 @@ export class PageObject {
async selectModel({ provider, model }: { provider: string; model: string }) {
await this.page.getByRole("button", { name: "Model: Auto" }).click();
await this.page.getByText(provider).click();
await this.page.getByText(provider, { exact: true }).click();
await this.page.getByText(model, { exact: true }).click();
}
@@ -701,6 +701,23 @@ export class PageObject {
.click();
}
async selectTestAzureModel() {
await this.page.getByRole("button", { name: "Model: Auto" }).click();
await this.page.getByText("Azure OpenAI", { exact: true }).click();
await this.page.getByText("GPT-5", { exact: true }).click();
}
async setUpAzure({ autoApprove = false }: { autoApprove?: boolean } = {}) {
await this.githubConnector.clearPushEvents();
await this.goToSettingsTab();
if (autoApprove) {
await this.toggleAutoApprove();
}
// Azure should already be configured via environment variables
// so we don't need additional setup steps like setUpDyadProvider
await this.goToAppsTab();
}
async setUpTestProvider() {
await this.page.getByText("Add custom providerConnect to").click();
// Fill out provider dialog

View File

@@ -0,0 +1,6 @@
- paragraph: tc=basic
- paragraph: This is a simple basic response
- img
- text: less than a minute ago
- button "Retry":
- img