Repository: nanobrowser/nanobrowser
Pull request #97: Add Azure OpenAI and OpenRouter Provider Support
Author: login: bsormagec
Created: 2025-04-10T05:31:58Z | Merged: 2025-04-26T04:21:13Z

  This PR adds support for using Azure OpenAI and OpenRouter as LLM providers within the extension.
  
  - Users can now configure their Azure OpenAI endpoints, deployment names, API versions, and keys in the settings.
  
  - OpenRouter configuration is also added, allowing users to leverage its API.
  - Added OpenRouter and auto-fetch and filter models, including only the free ones!
  
  - The underlying code for managing provider configurations and creating chat models has been updated to handle these new provider types.
  
  fixes #69 

Linked issue #69: [Feature]: Support Azure OpenAI
### What problem does this solve?

Azure OpenAI has different API params comparing with OpenAI, it will be great if nanobrowser can also support AzureOpenAI model configs.

### Proposed solution

Add AzureOpenAI as model provider

### Additional context

_No response_
Linked issue comments: user: login: surim0n
created_at: 2025-03-27T13:19:40Z
body: I would also love this. 
author_association: NONE
user: login: LoadingZhang
created_at: 2025-05-15T06:46:50Z
body: @alexchenzl can't click save button

<img width="841" alt="Image" src="https://github.com/user-attachments/assets/5876d0a9-b143-4011-bb4f-7088789fe91a" />
author_association: NONE
user: login: phanthaiduong22
created_at: 2025-05-18T16:12:49Z
body: > can't click save button

Same to me.
cc @bsormagec 
author_association: NONE
user: login: bsormagec
created_at: 2025-05-18T16:19:00Z
body: On my version I tested all the possibilities. I am using macOS and chrome stable version. I don't know what changed i tested now delete and install again it worked. @alexchenzl did you see any problem ?
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-05-18T16:19:41Z
body: I see the problem, after you wrote your deployment name need to press enter to convert it tag.
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-05-18T16:19:50Z
body: > @alexchenzl can't click save button
> 
> <img width="841" alt="Image" src="https://github.com/user-attachments/assets/5876d0a9-b143-4011-bb4f-7088789fe91a" />

I see the problem, after you wrote your deployment name need to press enter to convert it tag.
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-05-18T16:20:27Z
body: It is writing under the input field already 😀 
author_association: CONTRIBUTOR

Diff:
diff --git a/chrome-extension/src/background/agent/helper.ts b/chrome-extension/src/background/agent/helper.ts
index f2b7d4a9..9fb118c8 100644
--- a/chrome-extension/src/background/agent/helper.ts
+++ b/chrome-extension/src/background/agent/helper.ts
@@ -1,5 +1,5 @@
 import { type ProviderConfig, type ModelConfig, ProviderTypeEnum } from '@extension/storage';
-import { ChatOpenAI } from '@langchain/openai';
+import { ChatOpenAI, AzureChatOpenAI } from '@langchain/openai';
 import { ChatAnthropic } from '@langchain/anthropic';
 import { ChatGoogleGenerativeAI } from '@langchain/google-genai';
 import { ChatXAI } from '@langchain/xai';
@@ -13,25 +13,56 @@ function isOpenAIOModel(modelName: string): boolean {
   return modelName.startsWith('openai/o') || modelName.startsWith('o');
 }
 
-function createOpenAIChatModel(providerConfig: ProviderConfig, modelConfig: ModelConfig): BaseChatModel {
+function createOpenAIChatModel(
+  providerConfig: ProviderConfig,
+  modelConfig: ModelConfig,
+  // Add optional extra fetch options for headers etc.
+  extraFetchOptions?: { headers?: Record<string, string> },
+): BaseChatModel {
   const args: {
     model: string;
     apiKey?: string;
-    configuration?: Record<string, unknown>;
-    modelKwargs?: { max_completion_tokens: number };
+    // Configuration should align with ClientOptions from @langchain/openai
+    configuration?: {
+      baseURL?: string;
+      defaultHeaders?: Record<string, string>;
+      // Add other ClientOptions if needed, e.g.?
+      // dangerouslyAllowBrowser?: boolean;
+    };
+    modelKwargs?: {
+      max_completion_tokens: number;
+      reasoning_effort?: 'low' | 'medium' | 'high';
+    };
     topP?: number;
     temperature?: number;
     maxTokens?: number;
   } = {
     model: modelConfig.modelName,
     apiKey: providerConfig.apiKey,
+    // Initialize configuration object
+    configuration: {},
   };
 
   if (providerConfig.baseUrl) {
-    args.configuration = {
-      baseURL: providerConfig.baseUrl,
+    // Set baseURL inside configuration
+    args.configuration!.baseURL = providerConfig.baseUrl;
+  }
+
+  // Always add custom headers for OpenRouter to identify Nanobrowser
+  if (providerConfig.type === ProviderTypeEnum.OpenRouter) {
+    args.configuration!.defaultHeaders = {
+      ...(args.configuration!.defaultHeaders || {}),
+      'HTTP-Referer': 'https://nanobrowser.ai',
+      'X-Title': 'Nanobrowser',
+      ...(extraFetchOptions?.headers || {}),
+    };
+  } else if (extraFetchOptions?.headers) {
+    args.configuration!.defaultHeaders = {
+      ...(args.configuration!.defaultHeaders || {}),
+      ...extraFetchOptions.headers,
     };
   }
+
   // custom provider may have no api key
   if (providerConfig.apiKey) {
     args.apiKey = providerConfig.apiKey;
@@ -42,22 +73,53 @@ function createOpenAIChatModel(providerConfig: ProviderConfig, modelConfig: Mode
     args.modelKwargs = {
       max_completion_tokens: maxTokens,
     };
+
+    // Add reasoning_effort parameter for o-series models if specified
+    if (modelConfig.reasoningEffort) {
+      args.modelKwargs.reasoning_effort = modelConfig.reasoningEffort;
+    }
   } else {
     args.topP = (modelConfig.parameters?.topP ?? 0.1) as number;
     args.temperature = (modelConfig.parameters?.temperature ?? 0.1) as number;
     args.maxTokens = maxTokens;
   }
+  // Log args being passed to ChatOpenAI constructor inside the helper
+  console.log('[createOpenAIChatModel] Args passed to new ChatOpenAI:', args);
   return new ChatOpenAI(args);
 }
 
+// Function to extract instance name from Azure endpoint URL
+function extractInstanceNameFromUrl(url: string): string | null {
+  try {
+    const parsedUrl = new URL(url);
+    const hostnameParts = parsedUrl.hostname.split('.');
+    // Expecting format like instance-name.openai.azure.com
+    if (hostnameParts.length >= 4 && hostnameParts[1] === 'openai' && hostnameParts[2] === 'azure') {
+      return hostnameParts[0];
+    }
+  } catch (e) {
+    console.error('Error parsing Azure endpoint URL:', e);
+  }
+  return null;
+}
+
+// Function to check if a provider ID is an Azure provider
+function isAzureProvider(providerId: string): boolean {
+  return providerId === ProviderTypeEnum.AzureOpenAI || providerId.startsWith(`${ProviderTypeEnum.AzureOpenAI}_`);
+}
+
 // create a chat model based on the agent name, the model name and provider
 export function createChatModel(providerConfig: ProviderConfig, modelConfig: ModelConfig): BaseChatModel {
   const temperature = (modelConfig.parameters?.temperature ?? 0.1) as number;
   const topP = (modelConfig.parameters?.topP ?? 0.1) as number;
 
+  // Check if the provider is an Azure provider with a custom ID (e.g. azure_openai_2)
+  const isAzure = isAzureProvider(modelConfig.provider);
+
   switch (modelConfig.provider) {
     case ProviderTypeEnum.OpenAI: {
-      return createOpenAIChatModel(providerConfig, modelConfig);
+      // Call helper without extra options
+      return createOpenAIChatModel(providerConfig, modelConfig, undefined);
     }
     case ProviderTypeEnum.Anthropic: {
       const args = {
@@ -125,9 +187,152 @@ export function createChatModel(providerConfig: ProviderConfig, modelConfig: Mod
       };
       return new ChatOllama(args);
     }
+    case ProviderTypeEnum.AzureOpenAI: {
+      // Validate necessary fields first
+      if (
+        !providerConfig.baseUrl ||
+        !providerConfig.azureDeploymentNames ||
+        providerConfig.azureDeploymentNames.length === 0 ||
+        !providerConfig.azureApiVersion ||
+        !providerConfig.apiKey
+      ) {
+        throw new Error(
+          'Azure configuration is incomplete. Endpoint, Deployment Name, API Version, and API Key are required. Please check settings.',
+        );
+      }
+
+      // Instead of always using the first deployment name, use the model name from modelConfig
+      // which contains the actual model selected in the UI
+      const deploymentName = modelConfig.modelName;
+
+      // Validate that the selected model exists in the configured deployments
+      if (!providerConfig.azureDeploymentNames.includes(deploymentName)) {
+        console.warn(
+          `[createChatModel] Selected deployment "${deploymentName}" not found in available deployments. ` +
+            `Available: ${JSON.stringify(providerConfig.azureDeploymentNames)}. Using the model anyway.`,
+        );
+      }
+
+      // Extract instance name from the endpoint URL
+      const instanceName = extractInstanceNameFromUrl(providerConfig.baseUrl);
+      if (!instanceName) {
+        throw new Error(
+          `Could not extract Instance Name from Azure Endpoint URL: ${providerConfig.baseUrl}. Expected format like https://<your-instance-name>.openai.azure.com/`,
+        );
+      }
+
+      // Check if the Azure deployment is using an "o" series model (GPT-4o, etc.)
+      const isOSeriesModel = isOpenAIOModel(deploymentName);
+
+      // Use AzureChatOpenAI with specific parameters
+      const args = {
+        azureOpenAIApiInstanceName: instanceName, // Derived from endpoint
+        azureOpenAIApiDeploymentName: deploymentName,
+        azureOpenAIApiKey: providerConfig.apiKey,
+        azureOpenAIApiVersion: providerConfig.azureApiVersion,
+        // For Azure, the model name should be the deployment name itself
+        model: deploymentName, // Set model = deployment name to fix Azure requests
+        // For O series models, use modelKwargs instead of temperature/topP
+        ...(isOSeriesModel
+          ? {
+              modelKwargs: {
+                max_completion_tokens: maxTokens,
+                // Add reasoning_effort parameter for Azure o-series models if specified
+                ...(modelConfig.reasoningEffort ? { reasoning_effort: modelConfig.reasoningEffort } : {}),
+              },
+            }
+          : {
+              temperature,
+              topP,
+              maxTokens,
+            }),
+        // DO NOT pass baseUrl or configuration here
+      };
+      console.log('[createChatModel] Azure args passed to AzureChatOpenAI:', args);
+      return new AzureChatOpenAI(args);
+    }
+    case ProviderTypeEnum.OpenRouter: {
+      // Call the helper function, passing OpenRouter headers via the third argument
+      console.log('[createChatModel] Calling createOpenAIChatModel for OpenRouter');
+      return createOpenAIChatModel(providerConfig, modelConfig, {
+        headers: {
+          'HTTP-Referer': 'nanobrowser-extension',
+          'X-Title': 'NanoBrowser Extension',
+        },
+      });
+    }
     default: {
+      // Check if this is a custom Azure provider (azure_openai_X)
+      if (isAzure) {
+        // Validate necessary fields first
+        if (
+          !providerConfig.baseUrl ||
+          !providerConfig.azureDeploymentNames ||
+          providerConfig.azureDeploymentNames.length === 0 ||
+          !providerConfig.azureApiVersion ||
+          !providerConfig.apiKey
+        ) {
+          throw new Error(
+            'Azure configuration is incomplete. Endpoint, Deployment Name, API Version, and API Key are required. Please check settings.',
+          );
+        }
+
+        // Instead of always using the first deployment name, use the model name from modelConfig
+        // which contains the actual model selected in the UI
+        const deploymentName = modelConfig.modelName;
+
+        // Validate that the selected model exists in the configured deployments
+        if (!providerConfig.azureDeploymentNames.includes(deploymentName)) {
+          console.warn(
+            `[createChatModel] Selected deployment "${deploymentName}" not found in available deployments. ` +
+              `Available: ${JSON.stringify(providerConfig.azureDeploymentNames)}. Using the model anyway.`,
+          );
+        }
+
+        // Extract instance name from the endpoint URL
+        const instanceName = extractInstanceNameFromUrl(providerConfig.baseUrl);
+        if (!instanceName) {
+          throw new Error(
+            `Could not extract Instance Name from Azure Endpoint URL: ${providerConfig.baseUrl}. Expected format like https://<your-instance-name>.openai.azure.com/`,
+          );
+        }
+
+        // Check if the Azure deployment is using an "o" series model (GPT-4o, etc.)
+        const isOSeriesModel = isOpenAIOModel(deploymentName);
+
+        // Use AzureChatOpenAI with specific parameters
+        const args = {
+          azureOpenAIApiInstanceName: instanceName, // Derived from endpoint
+          azureOpenAIApiDeploymentName: deploymentName,
+          azureOpenAIApiKey: providerConfig.apiKey,
+          azureOpenAIApiVersion: providerConfig.azureApiVersion,
+          // For Azure, the model name should be the deployment name itself
+          model: deploymentName, // Set model = deployment name to fix Azure requests
+          // For O series models, use modelKwargs instead of temperature/topP
+          ...(isOSeriesModel
+            ? {
+                modelKwargs: {
+                  max_completion_tokens: maxTokens,
+                  // Add reasoning_effort parameter for Azure o-series models if specified
+                  ...(modelConfig.reasoningEffort ? { reasoning_effort: modelConfig.reasoningEffort } : {}),
+                },
+              }
+            : {
+                temperature,
+                topP,
+                maxTokens,
+              }),
+          // DO NOT pass baseUrl or configuration here
+        };
+        console.log('[createChatModel] Azure args (custom ID) passed to AzureChatOpenAI:', args);
+        return new AzureChatOpenAI(args);
+      }
+
+      // If not Azure, handles CustomOpenAI
       // by default, we think it's a openai-compatible provider
-      return createOpenAIChatModel(providerConfig, modelConfig);
+      // Pass undefined for extraFetchOptions for default/custom cases
+      console.log('[createChatModel] Calling createOpenAIChatModel for default/custom provider');
+      return createOpenAIChatModel(providerConfig, modelConfig, undefined);
     }
   }
 }
diff --git a/chrome-extension/src/background/dom/views.ts b/chrome-extension/src/background/dom/views.ts
index d6609c1d..69c70db5 100644
--- a/chrome-extension/src/background/dom/views.ts
+++ b/chrome-extension/src/background/dom/views.ts
@@ -1,5 +1,4 @@
-import type { ViewportInfo, CoordinateSet } from './history/view';
-import type { HashedDomElement } from './history/view';
+import type { ViewportInfo, CoordinateSet, HashedDomElement } from './history/view';
 import { HistoryTreeProcessor } from './history/service';
 
 export abstract class DOMBaseNode {
diff --git a/chrome-extension/src/background/index.ts b/chrome-extension/src/background/index.ts
index 8dd44e23..7c805bc0 100644
--- a/chrome-extension/src/background/index.ts
+++ b/chrome-extension/src/background/index.ts
@@ -5,7 +5,7 @@ import { Executor } from './agent/executor';
 import { createLogger } from './log';
 import { ExecutionState } from './agent/event/types';
 import { createChatModel } from './agent/helper';
-import { BaseChatModel } from '@langchain/core/language_models/chat_models';
+import type { BaseChatModel } from '@langchain/core/language_models/chat_models';
 
 const logger = createLogger('background');
 
@@ -74,7 +74,14 @@ chrome.tabs.onRemoved.addListener(tabId => {
 
 logger.info('background loaded');
 
-// Setup connection listener
+// Listen for simple messages (e.g., from options page)
+chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
+  // Handle other message types if needed in the future
+  // Return false if response is not sent asynchronously
+  // return false;
+});
+
+// Setup connection listener for long-lived connections (e.g., side panel)
 chrome.runtime.onConnect.addListener(port => {
   if (port.name === 'side-panel-connection') {
     currentPort = port;
@@ -145,6 +152,7 @@ chrome.runtime.onConnect.addListener(port => {
             await currentExecutor.pause();
             return port.postMessage({ type: 'success' });
           }
+
           default:
             return port.postMessage({ type: 'error', error: 'Unknown message type' });
         }
@@ -182,18 +190,24 @@ async function setupExecutor(taskId: string, task: string, browserContext: Brows
   if (!navigatorModel) {
     throw new Error('Please choose a model for the navigator in the settings first');
   }
-  const navigatorLLM = createChatModel(providers[navigatorModel.provider], navigatorModel);
+  // Log the provider config being used for the navigator
+  const navigatorProviderConfig = providers[navigatorModel.provider];
+  const navigatorLLM = createChatModel(navigatorProviderConfig, navigatorModel);
 
   let plannerLLM: BaseChatModel | null = null;
   const plannerModel = agentModels[AgentNameEnum.Planner];
   if (plannerModel) {
-    plannerLLM = createChatModel(providers[plannerModel.provider], plannerModel);
+    // Log the provider config being used for the planner
+    const plannerProviderConfig = providers[plannerModel.provider];
+    plannerLLM = createChatModel(plannerProviderConfig, plannerModel);
   }
 
   let validatorLLM: BaseChatModel | null = null;
   const validatorModel = agentModels[AgentNameEnum.Validator];
   if (validatorModel) {
-    validatorLLM = createChatModel(providers[validatorModel.provider], validatorModel);
+    // Log the provider config being used for the validator
+    const validatorProviderConfig = providers[validatorModel.provider];
+    validatorLLM = createChatModel(validatorProviderConfig, validatorModel);
   }
 
   const generalSettings = await generalSettingsStore.getSettings();
diff --git a/packages/storage/lib/settings/agentModels.ts b/packages/storage/lib/settings/agentModels.ts
index eee85cf6..204f0887 100644
--- a/packages/storage/lib/settings/agentModels.ts
+++ b/packages/storage/lib/settings/agentModels.ts
@@ -10,6 +10,7 @@ export interface ModelConfig {
   provider: string;
   modelName: string;
   parameters?: Record<string, unknown>;
+  reasoningEffort?: 'low' | 'medium' | 'high'; // For o-series models (OpenAI and Azure)
 }
 
 // Interface for storing multiple agent model configurations
diff --git a/packages/storage/lib/settings/llmProviders.ts b/packages/storage/lib/settings/llmProviders.ts
index c926b6fb..af8e90ee 100644
--- a/packages/storage/lib/settings/llmProviders.ts
+++ b/packages/storage/lib/settings/llmProviders.ts
@@ -8,9 +8,12 @@ export interface ProviderConfig {
   name?: string; // Display name in the options
   type?: ProviderTypeEnum; // Help to decide which LangChain ChatModel package to use
   apiKey: string; // Must be provided, but may be empty for local models
-  baseUrl?: string; // Optional base URL if provided
-  modelNames?: string[]; // Chosen model names, if not provided use hardcoded names from llmProviderModelNames
+  baseUrl?: string; // Optional base URL if provided // For Azure: Endpoint
+  modelNames?: string[]; // Chosen model names (NOT used for Azure OpenAI)
   createdAt?: number; // Timestamp in milliseconds when the provider was created
+  // Azure Specific Fields:
+  azureDeploymentNames?: string[]; // Azure deployment names array
+  azureApiVersion?: string;
 }
 
 // Interface for storing multiple LLM provider configurations
@@ -41,6 +44,17 @@ const storage = createStorage<LLMKeyRecord>(
 // Helper function to determine provider type from provider name
 // Make sure to update this function if you add a new provider type
 export function getProviderTypeByProviderId(providerId: string): ProviderTypeEnum {
+  // Check if this is an Azure provider (either the main one or one with a custom ID)
+  if (providerId === ProviderTypeEnum.AzureOpenAI) {
+    return ProviderTypeEnum.AzureOpenAI;
+  }
+
+  // Handle custom Azure providers with IDs like azure_openai_2
+  if (typeof providerId === 'string' && providerId.startsWith(`${ProviderTypeEnum.AzureOpenAI}_`)) {
+    return ProviderTypeEnum.AzureOpenAI;
+  }
+
+  // Handle standard provider types
   switch (providerId) {
     case ProviderTypeEnum.OpenAI:
     case ProviderTypeEnum.Anthropic:
@@ -48,6 +62,7 @@ export function getProviderTypeByProviderId(providerId: string): ProviderTypeEnu
     case ProviderTypeEnum.Gemini:
     case ProviderTypeEnum.Grok:
     case ProviderTypeEnum.Ollama:
+    case ProviderTypeEnum.OpenRouter:
       return providerId;
     default:
       return ProviderTypeEnum.CustomOpenAI;
@@ -70,13 +85,16 @@ export function getDefaultDisplayNameFromProviderId(providerId: string): string
       return 'Grok';
     case ProviderTypeEnum.Ollama:
       return 'Ollama';
+    case ProviderTypeEnum.AzureOpenAI:
+      return 'Azure OpenAI';
+    case ProviderTypeEnum.OpenRouter:
+      return 'OpenRouter';
     default:
       return providerId; // Use the provider id as display name for custom providers by default
   }
 }
 
 // Get default configuration for built-in providers
-// Make sure to update this function if you add a new provider type
 export function getDefaultProviderConfig(providerId: string): ProviderConfig {
   switch (providerId) {
     case ProviderTypeEnum.OpenAI:
@@ -84,10 +102,12 @@ export function getDefaultProviderConfig(providerId: string): ProviderConfig {
     case ProviderTypeEnum.DeepSeek:
     case ProviderTypeEnum.Gemini:
     case ProviderTypeEnum.Grok:
+    case ProviderTypeEnum.OpenRouter: // OpenRouter uses modelNames
       return {
         apiKey: '',
         name: getDefaultDisplayNameFromProviderId(providerId),
         type: providerId,
+        baseUrl: providerId === ProviderTypeEnum.OpenRouter ? 'https://openrouter.ai/api/v1' : undefined,
         modelNames: [...(llmProviderModelNames[providerId] || [])],
         createdAt: Date.now(),
       };
@@ -97,17 +117,28 @@ export function getDefaultProviderConfig(providerId: string): ProviderConfig {
         apiKey: 'ollama', // Set default API key for Ollama
         name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.Ollama),
         type: ProviderTypeEnum.Ollama,
-        modelNames: [],
+        modelNames: [], // Ollama uses modelNames (user adds them)
         baseUrl: 'http://localhost:11434',
         createdAt: Date.now(),
       };
-    default:
+    case ProviderTypeEnum.AzureOpenAI:
+      return {
+        apiKey: '', // User needs to provide API Key
+        name: getDefaultDisplayNameFromProviderId(ProviderTypeEnum.AzureOpenAI),
+        type: ProviderTypeEnum.AzureOpenAI,
+        baseUrl: '', // User needs to provide Azure endpoint
+        // modelNames: [], // Not used for Azure configuration
+        azureDeploymentNames: [], // Azure deployment names
+        azureApiVersion: '2024-02-15-preview', // Provide a common default API version
+        createdAt: Date.now(),
+      };
+    default: // Handles CustomOpenAI
       return {
         apiKey: '',
         name: getDefaultDisplayNameFromProviderId(providerId),
         type: ProviderTypeEnum.CustomOpenAI,
         baseUrl: '',
-        modelNames: [],
+        modelNames: [], // Custom providers use modelNames
         createdAt: Date.now(),
       };
   }
@@ -123,20 +154,53 @@ export function getDefaultAgentModelParams(providerId: string, agentName: AgentN
 
 // Helper function to ensure backward compatibility for provider configs
 function ensureBackwardCompatibility(providerId: string, config: ProviderConfig): ProviderConfig {
+  // Log input config
+  // console.log(`[ensureBackwardCompatibility] Input for ${providerId}:`, JSON.stringify(config));
+
   const updatedConfig = { ...config };
+
+  // Ensure name exists
   if (!updatedConfig.name) {
     updatedConfig.name = getDefaultDisplayNameFromProviderId(providerId);
   }
+  // Ensure type exists
   if (!updatedConfig.type) {
     updatedConfig.type = getProviderTypeByProviderId(providerId);
   }
-  if (!updatedConfig.modelNames) {
-    updatedConfig.modelNames = llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
+
+  // Handle Azure specifics
+  if (updatedConfig.type === ProviderTypeEnum.AzureOpenAI) {
+    // Ensure Azure fields exist, provide defaults if missing
+    if (updatedConfig.azureApiVersion === undefined) {
+      // console.log(`[ensureBackwardCompatibility] Adding default azureApiVersion for ${providerId}`);
+      updatedConfig.azureApiVersion = '2024-02-15-preview';
+    }
+
+    // Initialize azureDeploymentNames array if it doesn't exist yet
+    if (!updatedConfig.azureDeploymentNames) {
+      updatedConfig.azureDeploymentNames = [];
+    }
+
+    // CRITICAL: Delete modelNames if it exists for Azure type to clean up old configs
+    if (Object.prototype.hasOwnProperty.call(updatedConfig, 'modelNames')) {
+      // console.log(`[ensureBackwardCompatibility] Deleting modelNames for Azure config ${providerId}`);
+      delete updatedConfig.modelNames;
+    }
+  } else {
+    // Ensure modelNames exists ONLY for non-Azure types
+    if (!updatedConfig.modelNames) {
+      // console.log(`[ensureBackwardCompatibility] Adding default modelNames for non-Azure ${providerId}`);
+      updatedConfig.modelNames = llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
+    }
   }
+
+  // Ensure createdAt exists
   if (!updatedConfig.createdAt) {
-    // if createdAt is not set, set it to "03/04/2025" for backward compatibility
     updatedConfig.createdAt = new Date('03/04/2025').getTime();
   }
+
+  // Log output config
+  // console.log(`[ensureBackwardCompatibility] Output for ${providerId}:`, JSON.stringify(updatedConfig));
   return updatedConfig;
 }
 
@@ -151,19 +215,51 @@ export const llmProviderStore: LLMProviderStorage = {
       throw new Error('API key must be provided (can be empty for local models)');
     }
 
-    if (!config.modelNames) {
-      throw new Error('Model names must be provided');
+    const providerType = config.type || getProviderTypeByProviderId(providerId);
+
+    if (providerType === ProviderTypeEnum.AzureOpenAI) {
+      if (!config.baseUrl?.trim()) {
+        throw new Error('Azure Endpoint (baseUrl) is required');
+      }
+      if (!config.azureDeploymentNames || config.azureDeploymentNames.length === 0) {
+        throw new Error('At least one Azure Deployment Name is required');
+      }
+      if (!config.azureApiVersion?.trim()) {
+        throw new Error('Azure API Version is required');
+      }
+      if (!config.apiKey?.trim()) {
+        throw new Error('API Key is required for Azure OpenAI');
+      }
+    } else if (providerType !== ProviderTypeEnum.CustomOpenAI && providerType !== ProviderTypeEnum.Ollama) {
+      if (!config.apiKey?.trim()) {
+        throw new Error(`API Key is required for ${getDefaultDisplayNameFromProviderId(providerId)}`);
+      }
+    }
+
+    if (providerType !== ProviderTypeEnum.AzureOpenAI) {
+      if (!config.modelNames || config.modelNames.length === 0) {
+        console.warn(`Provider ${providerId} of type ${providerType} is being saved without model names.`);
+      }
     }
 
-    // Ensure backward compatibility by filling in missing fields
     const completeConfig: ProviderConfig = {
-      ...config,
+      apiKey: config.apiKey || '',
+      baseUrl: config.baseUrl,
       name: config.name || getDefaultDisplayNameFromProviderId(providerId),
-      type: config.type || getProviderTypeByProviderId(providerId),
-      modelNames: config.modelNames,
+      type: providerType,
       createdAt: config.createdAt || Date.now(),
+      ...(providerType === ProviderTypeEnum.AzureOpenAI
+        ? {
+            azureDeploymentNames: config.azureDeploymentNames || [],
+            azureApiVersion: config.azureApiVersion,
+          }
+        : {
+            modelNames: config.modelNames || [],
+          }),
     };
 
+    console.log(`[llmProviderStore.setProvider] Saving config for ${providerId}:`, JSON.stringify(completeConfig));
+
     const current = (await storage.get()) || { providers: {} };
     await storage.set({
       providers: {
diff --git a/packages/storage/lib/settings/types.ts b/packages/storage/lib/settings/types.ts
index 314f2742..667e23b8 100644
--- a/packages/storage/lib/settings/types.ts
+++ b/packages/storage/lib/settings/types.ts
@@ -15,6 +15,8 @@ export enum ProviderTypeEnum {
   Gemini = 'gemini',
   Grok = 'grok',
   Ollama = 'ollama',
+  AzureOpenAI = 'azure_openai',
+  OpenRouter = 'openrouter',
   CustomOpenAI = 'custom_openai',
 }
 
@@ -31,6 +33,18 @@ export const llmProviderModelNames = {
   ],
   [ProviderTypeEnum.Grok]: ['grok-2', 'grok-2-vision'],
   [ProviderTypeEnum.Ollama]: [],
+  [ProviderTypeEnum.AzureOpenAI]: ['gpt-4o', 'gpt-4o-mini', 'o3-mini', 'gpt-4.1', 'gpt-4.1-mini', 'gpt-4.1-nano'],
+  [ProviderTypeEnum.OpenRouter]: [
+    'openai/gpt-4o-2024-11-20',
+    'openai/gpt-4.1',
+    'openai/gpt-4.1-mini',
+    'openai/gpt-4.1-nano',
+    'openai/o4-mini',
+    'anthropic/claude-3.5-sonnet',
+    'anthropic/claude-3.7-sonnet',
+    'google/gemini-2.0-flash-001',
+    'deepseek/deepseek-chat-v3-0324:free',
+  ],
   // Custom OpenAI providers don't have predefined models as they are user-defined
 };
 
@@ -106,4 +120,32 @@ export const llmProviderParameters = {
       topP: 0.001,
     },
   },
+  [ProviderTypeEnum.AzureOpenAI]: {
+    [AgentNameEnum.Planner]: {
+      temperature: 0.01,
+      topP: 0.001,
+    },
+    [AgentNameEnum.Navigator]: {
+      temperature: 0,
+      topP: 0.001,
+    },
+    [AgentNameEnum.Validator]: {
+      temperature: 0,
+      topP: 0.001,
+    },
+  },
+  [ProviderTypeEnum.OpenRouter]: {
+    [AgentNameEnum.Planner]: {
+      temperature: 0.01,
+      topP: 0.001,
+    },
+    [AgentNameEnum.Navigator]: {
+      temperature: 0,
+      topP: 0.001,
+    },
+    [AgentNameEnum.Validator]: {
+      temperature: 0,
+      topP: 0.001,
+    },
+  },
 };
diff --git a/pages/options/src/components/ModelSettings.tsx b/pages/options/src/components/ModelSettings.tsx
index 41c02d36..53c47550 100644
--- a/pages/options/src/components/ModelSettings.tsx
+++ b/pages/options/src/components/ModelSettings.tsx
@@ -1,3 +1,11 @@
+/*
+ * Changes:
+ * - Added a searchable select component with filtering capability for model selection
+ * - Implemented keyboard navigation and accessibility for the custom dropdown
+ * - Added search functionality that filters models based on user input
+ * - Added keyboard event handlers to close dropdowns with Escape key
+ * - Styling for both light and dark mode themes
+ */
 import { useEffect, useState, useRef, useCallback } from 'react';
 import type { KeyboardEvent } from 'react';
 import { Button } from '@extension/ui';
@@ -10,26 +18,22 @@ import {
   getDefaultDisplayNameFromProviderId,
   getDefaultProviderConfig,
   getDefaultAgentModelParams,
+  type ProviderConfig,
 } from '@extension/storage';
+// Import chrome for messaging
+const IS_CHROME = typeof chrome !== 'undefined' && typeof chrome.runtime !== 'undefined';
+
+// Helper function to check if a model is an O-series model
+function isOpenAIOModel(modelName: string): boolean {
+  return modelName.startsWith('openai/o') || modelName.startsWith('o');
+}
 
 interface ModelSettingsProps {
-  isDarkMode?: boolean;
+  isDarkMode?: boolean; // Controls dark/light theme styling
 }
 
 export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
-  const [providers, setProviders] = useState<
-    Record<
-      string,
-      {
-        apiKey: string;
-        baseUrl?: string;
-        name?: string;
-        modelNames?: string[];
-        type?: ProviderTypeEnum;
-        createdAt?: number;
-      }
-    >
-  >({});
+  const [providers, setProviders] = useState<Record<string, ProviderConfig>>({});
   const [modifiedProviders, setModifiedProviders] = useState<Set<string>>(new Set());
   const [providersFromStorage, setProvidersFromStorage] = useState<Set<string>>(new Set());
   const [selectedModels, setSelectedModels] = useState<Record<AgentNameEnum, string>>({
@@ -42,6 +46,13 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
     [AgentNameEnum.Planner]: { temperature: 0, topP: 0 },
     [AgentNameEnum.Validator]: { temperature: 0, topP: 0 },
   });
+
+  // State for reasoning effort for O-series models
+  const [reasoningEffort, setReasoningEffort] = useState<Record<AgentNameEnum, 'low' | 'medium' | 'high' | undefined>>({
+    [AgentNameEnum.Navigator]: undefined,
+    [AgentNameEnum.Planner]: undefined,
+    [AgentNameEnum.Validator]: undefined,
+  });
   const [newModelInputs, setNewModelInputs] = useState<Record<string, string>>({});
   const [isProviderSelectorOpen, setIsProviderSelectorOpen] = useState(false);
   const newlyAddedProviderRef = useRef<string | null>(null);
@@ -52,6 +63,7 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
   const [availableModels, setAvailableModels] = useState<
     Array<{ provider: string; providerName: string; model: string }>
   >([]);
+  // State for model input handling
 
   useEffect(() => {
     const loadProviders = async () => {
@@ -100,6 +112,13 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
                 },
               }));
             }
+            // Also load reasoningEffort if available
+            if (config.reasoningEffort) {
+              setReasoningEffort(prev => ({
+                ...prev,
+                [agent]: config.reasoningEffort as 'low' | 'medium' | 'high',
+              }));
+            }
           }
         }
         setSelectedModels(models);
@@ -162,15 +181,29 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
 
       // Only use providers that are actually in storage
       for (const [provider, config] of Object.entries(storedProviders)) {
-        const providerModels =
-          config.modelNames || llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [];
-        models.push(
-          ...providerModels.map(model => ({
-            provider,
-            providerName: config.name || provider,
-            model,
-          })),
-        );
+        if (config.type === ProviderTypeEnum.AzureOpenAI) {
+          // Handle Azure providers specially - use deployment names as models
+          const deploymentNames = config.azureDeploymentNames || [];
+
+          models.push(
+            ...deploymentNames.map(deployment => ({
+              provider,
+              providerName: config.name || provider,
+              model: deployment,
+            })),
+          );
+        } else {
+          // Standard handling for non-Azure providers
+          const providerModels =
+            config.modelNames || llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [];
+          models.push(
+            ...providerModels.map(model => ({
+              provider,
+              providerName: config.name || provider,
+              model,
+            })),
+          );
+        }
       }
     } catch (error) {
       console.error('Error loading providers for model selection:', error);
@@ -305,7 +338,6 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
   const getButtonProps = (provider: string) => {
     const isInStorage = providersFromStorage.has(provider);
     const isModified = modifiedProviders.has(provider);
-    const isCustom = providers[provider]?.type === ProviderTypeEnum.CustomOpenAI;
 
     // For deletion, we only care if it's in storage and not modified
     if (isInStorage && !isModified) {
@@ -318,8 +350,28 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
     }
 
     // For saving, we need to check if it has the required inputs
-    // Only custom providers can be saved without an API key
-    const hasInput = isCustom || Boolean(providers[provider]?.apiKey?.trim());
+    let hasInput = false;
+    const providerType = providers[provider]?.type;
+    const config = providers[provider];
+
+    if (providerType === ProviderTypeEnum.CustomOpenAI) {
+      hasInput = Boolean(config?.baseUrl?.trim()); // Custom needs Base URL, name checked elsewhere
+    } else if (providerType === ProviderTypeEnum.Ollama) {
+      hasInput = Boolean(config?.baseUrl?.trim()); // Ollama needs Base URL
+    } else if (providerType === ProviderTypeEnum.AzureOpenAI) {
+      // Azure needs API Key, Endpoint, Deployment Names, and API Version
+      hasInput =
+        Boolean(config?.apiKey?.trim()) &&
+        Boolean(config?.baseUrl?.trim()) &&
+        Boolean(config?.azureDeploymentNames?.length) &&
+        Boolean(config?.azureApiVersion?.trim());
+    } else if (providerType === ProviderTypeEnum.OpenRouter) {
+      // OpenRouter needs API Key and optionally Base URL (has default)
+      hasInput = Boolean(config?.apiKey?.trim()) && Boolean(config?.baseUrl?.trim());
+    } else {
+      // Other built-in providers just need API Key
+      hasInput = Boolean(config?.apiKey?.trim());
+    }
 
     return {
       theme: isDarkMode ? 'dark' : 'light',
@@ -340,20 +392,15 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
         return;
       }
 
-      // Check if base URL is required but missing for custom_openai
+      // Check if base URL is required but missing for custom_openai, ollama, azure_openai or openrouter
       if (
-        providers[provider].type === ProviderTypeEnum.CustomOpenAI &&
+        (providers[provider].type === ProviderTypeEnum.CustomOpenAI ||
+          providers[provider].type === ProviderTypeEnum.Ollama ||
+          providers[provider].type === ProviderTypeEnum.AzureOpenAI ||
+          providers[provider].type === ProviderTypeEnum.OpenRouter) &&
         (!providers[provider].baseUrl || !providers[provider].baseUrl.trim())
       ) {
-        alert('Base URL is required for custom OpenAI-compatible API providers');
-        return;
-      }
-
-      // Check if API key is required but empty for built-in providers (except custom)
-      const isCustom = providers[provider].type === ProviderTypeEnum.CustomOpenAI;
-
-      if (!isCustom && (!providers[provider].apiKey || !providers[provider].apiKey.trim())) {
-        alert('API key is required for this provider');
+        alert(`Base URL is required for ${getDefaultDisplayNameFromProviderId(provider)}. Please enter it.`);
         return;
       }
 
@@ -364,15 +411,31 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
         modelNames = [...(llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [])];
       }
 
-      // The provider store will handle filling in the missing fields
-      await llmProviderStore.setProvider(provider, {
-        apiKey: providers[provider].apiKey || '',
-        baseUrl: providers[provider].baseUrl,
-        name: providers[provider].name,
-        modelNames: modelNames,
-        type: providers[provider].type,
-        createdAt: providers[provider].createdAt,
-      });
+      // Prepare data for saving using the correctly typed config from state
+      // We can directly pass the relevant parts of the state config
+      // Create a copy to avoid modifying state directly if needed, though setProvider likely handles it
+      const configToSave: Partial<ProviderConfig> = { ...providers[provider] }; // Use Partial to allow deleting modelNames
+
+      // Explicitly set required fields that might be missing in partial state updates (though unlikely now)
+      configToSave.apiKey = providers[provider].apiKey || '';
+      configToSave.name = providers[provider].name || getDefaultDisplayNameFromProviderId(provider);
+      configToSave.type = providers[provider].type;
+      configToSave.createdAt = providers[provider].createdAt || Date.now();
+      // baseUrl, azureDeploymentName, azureApiVersion should be correctly set by handlers
+
+      if (providers[provider].type === ProviderTypeEnum.AzureOpenAI) {
+        // Ensure modelNames is NOT included for Azure
+        delete configToSave.modelNames;
+      } else {
+        // Ensure modelNames IS included for non-Azure
+        // Use existing modelNames from state, or default if somehow missing
+        configToSave.modelNames =
+          providers[provider].modelNames || llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [];
+      }
+
+      // Pass the cleaned config to setProvider
+      // Cast to ProviderConfig as we've ensured necessary fields based on type
+      await llmProviderStore.setProvider(provider, configToSave as ProviderConfig);
 
       // Clear any name errors on successful save
       setNameErrors(prev => {
@@ -452,6 +515,8 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
     // modelValue will be in format "provider>model"
     const [provider, model] = modelValue.split('>');
 
+    console.log(`[handleModelChange] Setting ${agentName} model: provider=${provider}, model=${model}`);
+
     // Set parameters based on provider type
     const newParameters = getDefaultAgentModelParams(provider, agentName);
 
@@ -467,10 +532,34 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
 
     try {
       if (model) {
+        const providerConfig = providers[provider];
+
+        // For Azure, verify the model is in the deployment names list
+        if (providerConfig && providerConfig.type === ProviderTypeEnum.AzureOpenAI) {
+          console.log(`[handleModelChange] Azure model selected: ${model}`);
+          console.log(`[handleModelChange] Available deployments:`, providerConfig.azureDeploymentNames || []);
+        }
+
+        // Reset reasoning effort if switching models
+        if (isOpenAIOModel(model)) {
+          // Keep existing reasoning effort if already set for O-series models
+          setReasoningEffort(prev => ({
+            ...prev,
+            [agentName]: prev[agentName] || 'medium', // Default to medium if not set
+          }));
+        } else {
+          // Clear reasoning effort for non-O-series models
+          setReasoningEffort(prev => ({
+            ...prev,
+            [agentName]: undefined,
+          }));
+        }
+
         await agentModelStore.setAgentModel(agentName, {
           provider,
           modelName: model,
           parameters: newParameters,
+          reasoningEffort: isOpenAIOModel(model) ? reasoningEffort[agentName] || 'medium' : undefined,
         });
       } else {
         // Reset storage if no model is selected
@@ -481,6 +570,32 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
     }
   };
 
+  const handleReasoningEffortChange = async (agentName: AgentNameEnum, value: 'low' | 'medium' | 'high') => {
+    setReasoningEffort(prev => ({
+      ...prev,
+      [agentName]: value,
+    }));
+
+    // Only update if we have a selected model
+    if (selectedModels[agentName] && isOpenAIOModel(selectedModels[agentName])) {
+      try {
+        // Find provider
+        const provider = getProviderForModel(selectedModels[agentName]);
+
+        if (provider) {
+          await agentModelStore.setAgentModel(agentName, {
+            provider,
+            modelName: selectedModels[agentName],
+            parameters: modelParameters[agentName],
+            reasoningEffort: value,
+          });
+        }
+      } catch (error) {
+        console.error('Error saving reasoning effort:', error);
+      }
+    }
+  };
+
   const handleParameterChange = async (agentName: AgentNameEnum, paramName: 'temperature' | 'topP', value: number) => {
     const newParameters = {
       ...modelParameters[agentName],
@@ -498,11 +613,23 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
         // Find provider
         let provider: string | undefined;
         for (const [providerKey, providerConfig] of Object.entries(providers)) {
-          const modelNames =
-            providerConfig.modelNames || llmProviderModelNames[providerKey as keyof typeof llmProviderModelNames] || [];
-          if (modelNames.includes(selectedModels[agentName])) {
-            provider = providerKey;
-            break;
+          if (providerConfig.type === ProviderTypeEnum.AzureOpenAI) {
+            // Check Azure deployment names
+            const deploymentNames = providerConfig.azureDeploymentNames || [];
+            if (deploymentNames.includes(selectedModels[agentName])) {
+              provider = providerKey;
+              break;
+            }
+          } else {
+            // Check standard model names for non-Azure providers
+            const modelNames =
+              providerConfig.modelNames ||
+              llmProviderModelNames[providerKey as keyof typeof llmProviderModelNames] ||
+              [];
+            if (modelNames.includes(selectedModels[agentName])) {
+              provider = providerKey;
+              break;
+            }
           }
         }
 
@@ -645,6 +772,28 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
             </div>
           </div>
         </div>
+
+        {/* Reasoning Effort Selector (only for O-series models) */}
+        {selectedModels[agentName] && isOpenAIOModel(selectedModels[agentName]) && (
+          <div className="flex items-center">
+            <label
+              htmlFor={`${agentName}-reasoning-effort`}
+              className={`w-24 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+              Reasoning
+            </label>
+            <div className="flex flex-1 items-center space-x-2">
+              <select
+                id={`${agentName}-reasoning-effort`}
+                value={reasoningEffort[agentName] || 'medium'}
+                onChange={e => handleReasoningEffortChange(agentName, e.target.value as 'low' | 'medium' | 'high')}
+                className={`flex-1 rounded-md border text-sm ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} px-3 py-2`}>
+                <option value="low">Low (Faster)</option>
+                <option value="medium">Medium (Balanced)</option>
+                <option value="high">High (More thorough)</option>
+              </select>
+            </div>
+          </div>
+        )}
       </div>
     </div>
   );
@@ -734,7 +883,13 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
   // Sort providers to ensure newly added providers appear at the bottom
   const getSortedProviders = () => {
     // Filter providers to only include those from storage and newly added providers
-    const filteredProviders = Object.entries(providers).filter(([providerId]) => {
+    const filteredProviders = Object.entries(providers).filter(([providerId, config]) => {
+      // ALSO filter out any provider missing a config or type, to satisfy TS
+      if (!config || !config.type) {
+        console.warn(`Filtering out provider ${providerId} with missing config or type.`);
+        return false;
+      }
+
       // Include if it's from storage
       if (providersFromStorage.has(providerId)) {
         return true;
@@ -751,7 +906,7 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
 
     // Sort the filtered providers
     return filteredProviders.sort(([keyA, configA], [keyB, configB]) => {
-      // First, separate newly added providers from stored providers
+      // Separate newly added providers from stored providers
       const isNewA = !providersFromStorage.has(keyA) && modifiedProviders.has(keyA);
       const isNewB = !providersFromStorage.has(keyB) && modifiedProviders.has(keyB);
 
@@ -795,21 +950,133 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
       return;
     }
 
+    // Handle Azure OpenAI specially to allow multiple instances
+    if (providerType === ProviderTypeEnum.AzureOpenAI) {
+      addAzureProvider();
+      return;
+    }
+
     // Handle built-in supported providers
     addBuiltInProvider(providerType);
   };
 
+  // New function to add Azure providers with unique IDs
+  const addAzureProvider = () => {
+    // Count existing Azure providers
+    const azureProviders = Object.keys(providers).filter(
+      key => key === ProviderTypeEnum.AzureOpenAI || key.startsWith(ProviderTypeEnum.AzureOpenAI + '_'),
+    );
+    const nextNumber = azureProviders.length + 1;
+
+    // Create unique ID
+    const providerId =
+      nextNumber === 1 ? ProviderTypeEnum.AzureOpenAI : `${ProviderTypeEnum.AzureOpenAI}_${nextNumber}`;
+
+    // Create config with appropriate name
+    const config = getDefaultProviderConfig(ProviderTypeEnum.AzureOpenAI);
+    config.name = `Azure OpenAI ${nextNumber}`;
+
+    // Add to providers
+    setProviders(prev => ({
+      ...prev,
+      [providerId]: config,
+    }));
+
+    setModifiedProviders(prev => new Set(prev).add(providerId));
+    newlyAddedProviderRef.current = providerId;
+
+    // Scroll to the newly added provider after render
+    setTimeout(() => {
+      const providerElement = document.getElementById(`provider-${providerId}`);
+      if (providerElement) {
+        providerElement.scrollIntoView({ behavior: 'smooth', block: 'center' });
+      }
+    }, 100);
+  };
+
   const getProviderForModel = (modelName: string): string => {
     for (const [provider, config] of Object.entries(providers)) {
-      const modelNames =
-        config.modelNames || llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [];
-      if (modelNames.includes(modelName)) {
-        return provider;
+      // Check Azure deployment names
+      if (config.type === ProviderTypeEnum.AzureOpenAI) {
+        const deploymentNames = config.azureDeploymentNames || [];
+        if (deploymentNames.includes(modelName)) {
+          return provider;
+        }
+      } else {
+        // Check regular model names for non-Azure providers
+        const modelNames =
+          config.modelNames || llmProviderModelNames[provider as keyof typeof llmProviderModelNames] || [];
+        if (modelNames.includes(modelName)) {
+          return provider;
+        }
       }
     }
     return '';
   };
 
+  // Add and remove Azure deployments
+  const addAzureDeployment = (provider: string, deploymentName: string) => {
+    if (!deploymentName.trim()) return;
+
+    setModifiedProviders(prev => new Set(prev).add(provider));
+    setProviders(prev => {
+      const providerData = prev[provider] || {};
+
+      // Initialize or use existing deploymentNames array
+      const deploymentNames = providerData.azureDeploymentNames || [];
+
+      // Don't add duplicates
+      if (deploymentNames.includes(deploymentName.trim())) return prev;
+
+      return {
+        ...prev,
+        [provider]: {
+          ...providerData,
+          azureDeploymentNames: [...deploymentNames, deploymentName.trim()],
+        },
+      };
+    });
+
+    // Clear the input
+    setNewModelInputs(prev => ({
+      ...prev,
+      [provider]: '',
+    }));
+  };
+
+  const removeAzureDeployment = (provider: string, deploymentToRemove: string) => {
+    setModifiedProviders(prev => new Set(prev).add(provider));
+
+    setProviders(prev => {
+      const providerData = prev[provider] || {};
+
+      // Get current deployments
+      const deploymentNames = providerData.azureDeploymentNames || [];
+
+      // Filter out the deployment to remove
+      const filteredDeployments = deploymentNames.filter(name => name !== deploymentToRemove);
+
+      return {
+        ...prev,
+        [provider]: {
+          ...providerData,
+          azureDeploymentNames: filteredDeployments,
+        },
+      };
+    });
+  };
+
+  const handleAzureApiVersionChange = (provider: string, apiVersion: string) => {
+    setModifiedProviders(prev => new Set(prev).add(provider));
+    setProviders(prev => ({
+      ...prev,
+      [provider]: {
+        ...prev[provider],
+        azureApiVersion: apiVersion.trim(),
+      },
+    }));
+  };
+
   return (
     <section className="space-y-6">
       {/* LLM Providers Section */}
@@ -824,265 +1091,410 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
               <p className="mb-4">No providers configured yet. Add a provider to get started.</p>
             </div>
           ) : (
-            getSortedProviders().map(([providerId, providerConfig]) => (
-              <div
-                key={providerId}
-                id={`provider-${providerId}`}
-                className={`space-y-4 ${modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) ? `rounded-lg border p-4 ${isDarkMode ? 'border-blue-700 bg-slate-700' : 'border-blue-200 bg-blue-50/70'}` : ''}`}>
-                <div className="flex items-center justify-between">
-                  <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                    {providerConfig.name || providerId}
-                  </h3>
-                  <div className="flex space-x-2">
-                    {/* Show Cancel button for newly added providers */}
-                    {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
-                      <Button variant="secondary" onClick={() => handleCancelProvider(providerId)}>
-                        Cancel
+            getSortedProviders().map(([providerId, providerConfig]) => {
+              // Add type guard to satisfy TypeScript
+              if (!providerConfig || !providerConfig.type) {
+                console.warn(`Skipping rendering for providerId ${providerId} due to missing config or type`);
+                return null; // Skip rendering this item if config/type is somehow missing
+              }
+
+              return (
+                <div
+                  key={providerId}
+                  id={`provider-${providerId}`}
+                  className={`space-y-4 ${modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) ? `rounded-lg border p-4 ${isDarkMode ? 'border-blue-700 bg-slate-700' : 'border-blue-200 bg-blue-50/70'}` : ''}`}>
+                  <div className="flex items-center justify-between">
+                    <h3 className={`text-lg font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                      {providerConfig.name || providerId}
+                    </h3>
+                    <div className="flex space-x-2">
+                      {/* Show Cancel button for newly added providers */}
+                      {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
+                        <Button variant="secondary" onClick={() => handleCancelProvider(providerId)}>
+                          Cancel
+                        </Button>
+                      )}
+                      <Button
+                        variant={getButtonProps(providerId).variant}
+                        disabled={getButtonProps(providerId).disabled}
+                        onClick={() =>
+                          providersFromStorage.has(providerId) && !modifiedProviders.has(providerId)
+                            ? handleDelete(providerId)
+                            : handleSave(providerId)
+                        }>
+                        {getButtonProps(providerId).children}
                       </Button>
-                    )}
-                    <Button
-                      variant={getButtonProps(providerId).variant}
-                      disabled={getButtonProps(providerId).disabled}
-                      onClick={() =>
-                        providersFromStorage.has(providerId) && !modifiedProviders.has(providerId)
-                          ? handleDelete(providerId)
-                          : handleSave(providerId)
-                      }>
-                      {getButtonProps(providerId).children}
-                    </Button>
+                    </div>
                   </div>
-                </div>
 
-                {/* Show message for newly added providers */}
-                {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
-                  <div className={`mb-2 text-sm ${isDarkMode ? 'text-teal-300' : 'text-teal-700'}`}>
-                    <p>This provider is newly added. Enter your API key and click Save to configure it.</p>
-                  </div>
-                )}
+                  {/* Show message for newly added providers */}
+                  {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
+                    <div className={`mb-2 text-sm ${isDarkMode ? 'text-teal-300' : 'text-teal-700'}`}>
+                      <p>This provider is newly added. Enter your API key and click Save to configure it.</p>
+                    </div>
+                  )}
 
-                <div className="space-y-3">
-                  {/* Name input (only for custom_openai) - moved to top for prominence */}
-                  {providerConfig.type === ProviderTypeEnum.CustomOpenAI && (
-                    <div className="flex flex-col">
-                      <div className="flex items-center">
-                        <label
-                          htmlFor={`${providerId}-name`}
-                          className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                          Name
-                        </label>
+                  <div className="space-y-3">
+                    {/* Name input (only for custom_openai) - moved to top for prominence */}
+                    {providerConfig.type === ProviderTypeEnum.CustomOpenAI && (
+                      <div className="flex flex-col">
+                        <div className="flex items-center">
+                          <label
+                            htmlFor={`${providerId}-name`}
+                            className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                            Name
+                          </label>
+                          <input
+                            id={`${providerId}-name`}
+                            type="text"
+                            placeholder="Provider name"
+                            value={providerConfig.name || ''}
+                            onChange={e => {
+                              console.log('Name input changed:', e.target.value);
+                              handleNameChange(providerId, e.target.value);
+                            }}
+                            className={`flex-1 rounded-md border p-2 text-sm ${
+                              nameErrors[providerId]
+                                ? isDarkMode
+                                  ? 'border-red-700 bg-slate-700 text-gray-200 focus:border-red-600 focus:ring-2 focus:ring-red-900'
+                                  : 'border-red-300 bg-gray-50 focus:border-red-400 focus:ring-2 focus:ring-red-200'
+                                : isDarkMode
+                                  ? 'border-blue-700 bg-slate-700 text-gray-200 focus:border-blue-600 focus:ring-2 focus:ring-blue-900'
+                                  : 'border-blue-300 bg-gray-50 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'
+                            } outline-none`}
+                          />
+                        </div>
+                        {nameErrors[providerId] ? (
+                          <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-red-400' : 'text-red-500'}`}>
+                            {nameErrors[providerId]}
+                          </p>
+                        ) : (
+                          <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                            Provider name (spaces are not allowed when saving)
+                          </p>
+                        )}
+                      </div>
+                    )}
+
+                    {/* API Key input with label */}
+                    <div className="flex items-center">
+                      <label
+                        htmlFor={`${providerId}-api-key`}
+                        className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                        API Key
+                        {/* Show asterisk only if required */}
+                        {providerConfig.type !== ProviderTypeEnum.CustomOpenAI &&
+                        providerConfig.type !== ProviderTypeEnum.Ollama
+                          ? '*'
+                          : ''}
+                      </label>
+                      <div className="relative flex-1">
                         <input
-                          id={`${providerId}-name`}
-                          type="text"
-                          placeholder="Provider name"
-                          value={providerConfig.name || ''}
-                          onChange={e => {
-                            console.log('Name input changed:', e.target.value);
-                            handleNameChange(providerId, e.target.value);
-                          }}
-                          className={`flex-1 rounded-md border p-2 text-sm ${
-                            nameErrors[providerId]
-                              ? isDarkMode
-                                ? 'border-red-700 bg-slate-700 text-gray-200 focus:border-red-600 focus:ring-2 focus:ring-red-900'
-                                : 'border-red-300 bg-gray-50 focus:border-red-400 focus:ring-2 focus:ring-red-200'
-                              : isDarkMode
-                                ? 'border-blue-700 bg-slate-700 text-gray-200 focus:border-blue-600 focus:ring-2 focus:ring-blue-900'
-                                : 'border-blue-300 bg-gray-50 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'
-                          } outline-none`}
+                          id={`${providerId}-api-key`}
+                          type="password"
+                          placeholder={
+                            providerConfig.type === ProviderTypeEnum.CustomOpenAI
+                              ? `${providerConfig.name || providerId} API key (optional)`
+                              : providerConfig.type === ProviderTypeEnum.Ollama
+                                ? 'API Key (leave empty for Ollama)'
+                                : `${providerConfig.name || providerId} API key (required)`
+                          }
+                          value={providerConfig.apiKey || ''}
+                          onChange={e => handleApiKeyChange(providerId, e.target.value, providerConfig.baseUrl)}
+                          className={`w-full rounded-md border text-sm ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
                         />
+                        {/* Show eye button only for newly added providers */}
+                        {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
+                          <button
+                            type="button"
+                            className={`absolute right-2 top-1/2 -translate-y-1/2 ${
+                              isDarkMode ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'
+                            }`}
+                            onClick={() => toggleApiKeyVisibility(providerId)}
+                            aria-label={visibleApiKeys[providerId] ? 'Hide API key' : 'Show API key'}>
+                            <svg
+                              xmlns="http://www.w3.org/2000/svg"
+                              viewBox="0 0 24 24"
+                              fill="none"
+                              stroke="currentColor"
+                              strokeWidth="2"
+                              strokeLinecap="round"
+                              strokeLinejoin="round"
+                              className="size-5"
+                              aria-hidden="true">
+                              <title>{visibleApiKeys[providerId] ? 'Hide API key' : 'Show API key'}</title>
+                              {visibleApiKeys[providerId] ? (
+                                <>
+                                  <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
+                                  <circle cx="12" cy="12" r="3" />
+                                  <line x1="2" y1="22" x2="22" y2="2" />
+                                </>
+                              ) : (
+                                <>
+                                  <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
+                                  <circle cx="12" cy="12" r="3" />
+                                </>
+                              )}
+                            </svg>
+                          </button>
+                        )}
                       </div>
-                      {nameErrors[providerId] ? (
-                        <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-red-400' : 'text-red-500'}`}>
-                          {nameErrors[providerId]}
-                        </p>
-                      ) : (
-                        <p className={`ml-20 mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
-                          Provider name (spaces are not allowed when saving)
-                        </p>
-                      )}
                     </div>
-                  )}
 
-                  {/* API Key input with label */}
-                  <div className="flex items-center">
-                    <label
-                      htmlFor={`${providerId}-api-key`}
-                      className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                      Key{providerConfig.type !== ProviderTypeEnum.CustomOpenAI ? '*' : ''}
-                    </label>
-                    <div className="relative flex-1">
-                      <input
-                        id={`${providerId}-api-key`}
-                        type="password"
-                        placeholder={
-                          providerConfig.type === ProviderTypeEnum.CustomOpenAI
-                            ? `${providerConfig.name || providerId} API key (optional)`
-                            : `${providerConfig.name || providerId} API key (required)`
-                        }
-                        value={providerConfig.apiKey || ''}
-                        onChange={e => handleApiKeyChange(providerId, e.target.value, providerConfig.baseUrl)}
-                        className={`w-full rounded-md border text-sm ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
-                      />
-                      {/* Show eye button only for newly added providers */}
-                      {modifiedProviders.has(providerId) && !providersFromStorage.has(providerId) && (
-                        <button
-                          type="button"
-                          className={`absolute right-2 top-1/2 -translate-y-1/2 ${
-                            isDarkMode ? 'text-gray-400 hover:text-gray-300' : 'text-gray-500 hover:text-gray-700'
-                          }`}
-                          onClick={() => toggleApiKeyVisibility(providerId)}
-                          aria-label={visibleApiKeys[providerId] ? 'Hide API key' : 'Show API key'}>
-                          <svg
-                            xmlns="http://www.w3.org/2000/svg"
-                            viewBox="0 0 24 24"
-                            fill="none"
-                            stroke="currentColor"
-                            strokeWidth="2"
-                            strokeLinecap="round"
-                            strokeLinejoin="round"
-                            className="h-5 w-5"
-                            aria-hidden="true">
-                            <title>{visibleApiKeys[providerId] ? 'Hide API key' : 'Show API key'}</title>
-                            {visibleApiKeys[providerId] ? (
-                              <>
-                                <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
-                                <circle cx="12" cy="12" r="3" />
-                                <line x1="2" y1="22" x2="22" y2="2" />
-                              </>
-                            ) : (
-                              <>
-                                <path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
-                                <circle cx="12" cy="12" r="3" />
-                              </>
-                            )}
-                          </svg>
-                        </button>
+                    {/* Display API key for newly added providers only when visible */}
+                    {modifiedProviders.has(providerId) &&
+                      !providersFromStorage.has(providerId) &&
+                      visibleApiKeys[providerId] &&
+                      providerConfig.apiKey && (
+                        <div className="ml-20 mt-1">
+                          <p
+                            className={`break-words font-mono text-sm ${isDarkMode ? 'text-emerald-400' : 'text-emerald-600'}`}>
+                            {providerConfig.apiKey}
+                          </p>
+                        </div>
                       )}
-                    </div>
-                  </div>
 
-                  {/* Display API key for newly added providers only when visible */}
-                  {modifiedProviders.has(providerId) &&
-                    !providersFromStorage.has(providerId) &&
-                    visibleApiKeys[providerId] &&
-                    providerConfig.apiKey && (
-                      <div className="ml-20 mt-1">
-                        <p
-                          className={`font-mono text-sm break-words ${isDarkMode ? 'text-emerald-400' : 'text-emerald-600'}`}>
-                          {providerConfig.apiKey}
-                        </p>
+                    {/* Base URL input (for custom_openai, ollama, azure_openai, and openrouter) */}
+                    {(providerConfig.type === ProviderTypeEnum.CustomOpenAI ||
+                      providerConfig.type === ProviderTypeEnum.Ollama ||
+                      providerConfig.type === ProviderTypeEnum.AzureOpenAI ||
+                      providerConfig.type === ProviderTypeEnum.OpenRouter) && (
+                      <div className="flex flex-col">
+                        <div className="flex items-center">
+                          <label
+                            htmlFor={`${providerId}-base-url`}
+                            className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                            {/* Adjust Label based on provider */}
+                            {providerConfig.type === ProviderTypeEnum.AzureOpenAI ? 'Endpoint' : 'Base URL'}
+                            {/* Show asterisk only if required */}
+                            {/* OpenRouter has a default, so not strictly required, but needed for save button */}
+                            {providerConfig.type === ProviderTypeEnum.CustomOpenAI ||
+                            providerConfig.type === ProviderTypeEnum.AzureOpenAI
+                              ? '*'
+                              : ''}
+                          </label>
+                          <input
+                            id={`${providerId}-base-url`}
+                            type="text"
+                            placeholder={
+                              providerConfig.type === ProviderTypeEnum.CustomOpenAI
+                                ? 'Required OpenAI-compatible API endpoint'
+                                : providerConfig.type === ProviderTypeEnum.AzureOpenAI
+                                  ? // Updated Azure placeholder
+                                    'https://YOUR_RESOURCE_NAME.openai.azure.com/'
+                                  : providerConfig.type === ProviderTypeEnum.OpenRouter
+                                    ? 'OpenRouter Base URL (optional, defaults to https://openrouter.ai/api/v1)'
+                                    : 'Ollama base URL'
+                            }
+                            value={providerConfig.baseUrl || ''}
+                            onChange={e => handleApiKeyChange(providerId, providerConfig.apiKey || '', e.target.value)}
+                            className={`flex-1 rounded-md border text-sm ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
+                          />
+                        </div>
                       </div>
                     )}
 
-                  {/* Base URL input (for custom_openai and ollama) */}
-                  {(providerConfig.type === ProviderTypeEnum.CustomOpenAI ||
-                    providerConfig.type === ProviderTypeEnum.Ollama) && (
-                    <div className="flex flex-col">
+                    {/* Azure Deployment Name input as tags/chips like OpenRouter models */}
+                    {(providerConfig.type as ProviderTypeEnum) === ProviderTypeEnum.AzureOpenAI && (
+                      <div className="flex items-start">
+                        <label
+                          htmlFor={`${providerId}-azure-deployment`}
+                          className={`w-20 pt-2 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                          Deployment*
+                        </label>
+                        <div className="flex-1 space-y-2">
+                          <div
+                            className={`flex min-h-[42px] flex-wrap items-center gap-2 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-2`}>
+                            {/* Show azure deployments */}
+                            {(providerConfig.azureDeploymentNames || []).length > 0
+                              ? (providerConfig.azureDeploymentNames || []).map((deploymentName: string) => (
+                                  <div
+                                    key={deploymentName}
+                                    className={`flex items-center rounded-full ${isDarkMode ? 'bg-blue-900 text-blue-100' : 'bg-blue-100 text-blue-800'} px-2 py-1 text-sm`}>
+                                    <span>{deploymentName}</span>
+                                    <button
+                                      type="button"
+                                      onClick={() => removeAzureDeployment(providerId, deploymentName)}
+                                      className={`ml-1 font-bold ${isDarkMode ? 'text-blue-300 hover:text-blue-100' : 'text-blue-600 hover:text-blue-800'}`}
+                                      aria-label={`Remove ${deploymentName}`}>
+                                      ×
+                                    </button>
+                                  </div>
+                                ))
+                              : null}
+                            <input
+                              id={`${providerId}-azure-deployment-input`}
+                              type="text"
+                              placeholder="Enter Azure model name (e.g. gpt-4o, gpt-4o-mini)"
+                              value={newModelInputs[providerId] || ''}
+                              onChange={e => handleModelsChange(providerId, e.target.value)}
+                              onKeyDown={e => {
+                                if (e.key === 'Enter' || e.key === ' ') {
+                                  e.preventDefault();
+                                  const value = newModelInputs[providerId] || '';
+                                  if (value.trim()) {
+                                    addAzureDeployment(providerId, value.trim());
+                                    // Clear the input
+                                    setNewModelInputs(prev => ({
+                                      ...prev,
+                                      [providerId]: '',
+                                    }));
+                                  }
+                                }
+                              }}
+                              className={`min-w-[150px] flex-1 border-none text-sm ${isDarkMode ? 'bg-transparent text-gray-200' : 'bg-transparent text-gray-700'} p-1 outline-none`}
+                            />
+                          </div>
+                          <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                            Type model name and press Enter or Space to set. Deployment name should match OpenAI model
+                            name (e.g., gpt-4o) for best compatibility.
+                          </p>
+                        </div>
+                      </div>
+                    )}
+
+                    {/* NEW: Azure API Version input */}
+                    {(providerConfig.type as ProviderTypeEnum) === ProviderTypeEnum.AzureOpenAI && (
                       <div className="flex items-center">
                         <label
-                          htmlFor={`${providerId}-base-url`}
+                          htmlFor={`${providerId}-azure-version`}
                           className={`w-20 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                          Base URL
-                          {providerConfig.type === ProviderTypeEnum.CustomOpenAI ||
-                          providerConfig.type === ProviderTypeEnum.Ollama
-                            ? '*'
-                            : ''}
+                          API Version*
                         </label>
                         <input
-                          id={`${providerId}-base-url`}
+                          id={`${providerId}-azure-version`}
                           type="text"
-                          placeholder={
-                            providerConfig.type === ProviderTypeEnum.CustomOpenAI
-                              ? 'Required for custom OpenAI-compatible API providers'
-                              : 'Ollama base URL'
-                          }
-                          value={providerConfig.baseUrl || ''}
-                          onChange={e => handleApiKeyChange(providerId, providerConfig.apiKey || '', e.target.value)}
+                          placeholder="e.g., 2024-02-15-preview" // Common example
+                          value={providerConfig.azureApiVersion || ''}
+                          onChange={e => handleAzureApiVersionChange(providerId, e.target.value)}
                           className={`flex-1 rounded-md border text-sm ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200 focus:border-blue-500 focus:ring-2 focus:ring-blue-800' : 'border-gray-300 bg-white text-gray-700 focus:border-blue-400 focus:ring-2 focus:ring-blue-200'} p-2 outline-none`}
                         />
                       </div>
-                    </div>
-                  )}
+                    )}
+
+                    {/* Models input section (for non-Azure providers) */}
+                    {(providerConfig.type as ProviderTypeEnum) !== ProviderTypeEnum.AzureOpenAI && (
+                      <div className="flex items-start">
+                        <label
+                          htmlFor={`${providerId}-models-label`}
+                          className={`w-20 pt-2 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
+                          Models
+                        </label>
+                        <div className="flex-1 space-y-2">
+                          {/* Conditional UI for OpenRouter */}
+                          {(providerConfig.type as ProviderTypeEnum) === ProviderTypeEnum.OpenRouter ? (
+                            <>
+                              <div
+                                className={`flex min-h-[42px] flex-wrap items-center gap-2 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-2`}>
+                                {providerConfig.modelNames && providerConfig.modelNames.length > 0 ? (
+                                  providerConfig.modelNames.map(model => (
+                                    <div
+                                      key={model}
+                                      className={`flex items-center rounded-full ${isDarkMode ? 'bg-blue-900 text-blue-100' : 'bg-blue-100 text-blue-800'} px-2 py-1 text-sm`}>
+                                      <span>{model}</span>
+                                      <button
+                                        type="button"
+                                        onClick={() => removeModel(providerId, model)}
+                                        className={`ml-1 font-bold ${isDarkMode ? 'text-blue-300 hover:text-blue-100' : 'text-blue-600 hover:text-blue-800'}`}
+                                        aria-label={`Remove ${model}`}>
+                                        ×
+                                      </button>
+                                    </div>
+                                  ))
+                                ) : (
+                                  <span className={`text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                                    No models selected. Add model names manually if needed.
+                                  </span>
+                                )}
+                                <input
+                                  id={`${providerId}-models-input`}
+                                  type="text"
+                                  placeholder=""
+                                  value={newModelInputs[providerId] || ''}
+                                  onChange={e => handleModelsChange(providerId, e.target.value)}
+                                  onKeyDown={e => handleKeyDown(e, providerId)}
+                                  className={`min-w-[150px] flex-1 border-none text-sm ${isDarkMode ? 'bg-transparent text-gray-200' : 'bg-transparent text-gray-700'} p-1 outline-none`}
+                                />
+                              </div>
+                              <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                                Type and Press Enter or Space to add.
+                              </p>
+                            </>
+                          ) : (
+                            /* Default Tag Input for other providers */
+                            <>
+                              <div
+                                className={`flex min-h-[42px] flex-wrap items-center gap-2 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-2`}>
+                                {(() => {
+                                  const models =
+                                    providerConfig.modelNames !== undefined
+                                      ? providerConfig.modelNames
+                                      : llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
+                                  return models.map(model => (
+                                    <div
+                                      key={model}
+                                      className={`flex items-center rounded-full ${isDarkMode ? 'bg-blue-900 text-blue-100' : 'bg-blue-100 text-blue-800'} px-2 py-1 text-sm`}>
+                                      <span>{model}</span>
+                                      <button
+                                        type="button"
+                                        onClick={() => removeModel(providerId, model)}
+                                        className={`ml-1 font-bold ${isDarkMode ? 'text-blue-300 hover:text-blue-100' : 'text-blue-600 hover:text-blue-800'}`}
+                                        aria-label={`Remove ${model}`}>
+                                        ×
+                                      </button>
+                                    </div>
+                                  ));
+                                })()}
+                                <input
+                                  id={`${providerId}-models-input`}
+                                  type="text"
+                                  placeholder=""
+                                  value={newModelInputs[providerId] || ''}
+                                  onChange={e => handleModelsChange(providerId, e.target.value)}
+                                  onKeyDown={e => handleKeyDown(e, providerId)}
+                                  className={`min-w-[150px] flex-1 border-none text-sm ${isDarkMode ? 'bg-transparent text-gray-200' : 'bg-transparent text-gray-700'} p-1 outline-none`}
+                                />
+                              </div>
+                              <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
+                                Type and Press Enter or Space to add.
+                              </p>
+                            </>
+                          )}
+                          {/* === END: Conditional UI === */}
+                        </div>
+                      </div>
+                    )}
 
-                  {/* Models input field with tags */}
-                  <div className="flex items-start">
-                    <label
-                      htmlFor={`${providerId}-models`}
-                      className={`w-20 pt-2 text-sm font-medium ${isDarkMode ? 'text-gray-300' : 'text-gray-700'}`}>
-                      Models
-                    </label>
-                    <div className="flex-1">
+                    {/* Ollama reminder at the bottom of the section */}
+                    {providerConfig.type === ProviderTypeEnum.Ollama && (
                       <div
-                        className={`flex min-h-[42px] flex-wrap items-center gap-2 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700 text-gray-200' : 'border-gray-300 bg-white text-gray-700'} p-2`}>
-                        {/* Display existing models as tags */}
-                        {(() => {
-                          // Get models from provider config or default models
-                          const models =
-                            providerConfig.modelNames !== undefined
-                              ? providerConfig.modelNames
-                              : llmProviderModelNames[providerId as keyof typeof llmProviderModelNames] || [];
-
-                          return models.map(model => (
-                            <div
-                              key={model}
-                              className={`flex items-center rounded-full ${isDarkMode ? 'bg-blue-900 text-blue-100' : 'bg-blue-100 text-blue-800'} px-2 py-1 text-sm`}>
-                              <span>{model}</span>
-                              <button
-                                type="button"
-                                onClick={() => removeModel(providerId, model)}
-                                className={`ml-1 font-bold ${isDarkMode ? 'text-blue-300 hover:text-blue-100' : 'text-blue-600 hover:text-blue-800'}`}
-                                aria-label={`Remove ${model}`}>
-                                ×
-                              </button>
-                            </div>
-                          ));
-                        })()}
-
-                        {/* Input for new models */}
-                        <input
-                          id={`${providerId}-models`}
-                          type="text"
-                          placeholder=""
-                          value={newModelInputs[providerId] || ''}
-                          onChange={e => handleModelsChange(providerId, e.target.value)}
-                          onKeyDown={e => handleKeyDown(e, providerId)}
-                          className={`min-w-[150px] flex-1 border-none text-sm ${isDarkMode ? 'bg-transparent text-gray-200' : 'bg-transparent text-gray-700'} p-1 outline-none`}
-                        />
+                        className={`mt-4 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700' : 'border-blue-100 bg-blue-50'} p-3`}>
+                        <p className={`text-sm ${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}>
+                          <strong>Remember:</strong> Add{' '}
+                          <code
+                            className={`rounded italic ${isDarkMode ? 'bg-slate-600 px-1 py-0.5' : 'bg-blue-100 px-1 py-0.5'}`}>
+                            OLLAMA_ORIGINS=chrome-extension://*
+                          </code>{' '}
+                          environment variable for the Ollama server.
+                          <a
+                            href="https://github.com/ollama/ollama/issues/6489"
+                            target="_blank"
+                            rel="noopener noreferrer"
+                            className={`ml-1 ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-800'}`}>
+                            Learn more
+                          </a>
+                        </p>
                       </div>
-                      <p className={`mt-1 text-xs ${isDarkMode ? 'text-gray-400' : 'text-gray-500'}`}>
-                        Type and Press Enter or Space to add a model
-                      </p>
-                    </div>
+                    )}
                   </div>
 
-                  {/* Ollama reminder at the bottom of the section */}
-                  {providerConfig.type === ProviderTypeEnum.Ollama && (
-                    <div
-                      className={`mt-4 rounded-md border ${isDarkMode ? 'border-slate-600 bg-slate-700' : 'border-blue-100 bg-blue-50'} p-3`}>
-                      <p className={`text-sm ${isDarkMode ? 'text-gray-200' : 'text-gray-700'}`}>
-                        <strong>Remember:</strong> Add{' '}
-                        <code
-                          className={`rounded italic ${isDarkMode ? 'bg-slate-600 px-1 py-0.5' : 'bg-blue-100 px-1 py-0.5'}`}>
-                          OLLAMA_ORIGINS=chrome-extension://*
-                        </code>{' '}
-                        environment variable for the Ollama server.
-                        <a
-                          href="https://github.com/ollama/ollama/issues/6489"
-                          target="_blank"
-                          rel="noopener noreferrer"
-                          className={`ml-1 ${isDarkMode ? 'text-blue-400 hover:text-blue-300' : 'text-blue-600 hover:text-blue-800'}`}>
-                          Learn more
-                        </a>
-                      </p>
-                    </div>
+                  {/* Add divider except for the last item */}
+                  {Object.keys(providers).indexOf(providerId) < Object.keys(providers).length - 1 && (
+                    <div className={`mt-4 border-t ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`} />
                   )}
                 </div>
-
-                {/* Add divider except for the last item */}
-                {Object.keys(providers).indexOf(providerId) < Object.keys(providers).length - 1 && (
-                  <div className={`mt-4 border-t ${isDarkMode ? 'border-gray-700' : 'border-gray-200'}`} />
-                )}
-              </div>
-            ))
+              );
+            })
           )}
 
           {/* Add Provider button and dropdown */}
@@ -1108,12 +1520,13 @@ export const ModelSettings = ({ isDarkMode = false }: ModelSettingsProps) => {
                 <div className="py-1">
                   {/* Map through provider types to create buttons */}
                   {Object.values(ProviderTypeEnum)
-                    // Filter out CustomOpenAI and already added providers
+                    // Allow Azure to appear multiple times, but filter out other already added providers
                     .filter(
                       type =>
-                        type !== ProviderTypeEnum.CustomOpenAI &&
-                        !providersFromStorage.has(type) &&
-                        !modifiedProviders.has(type),
+                        type === ProviderTypeEnum.AzureOpenAI || // Always show Azure
+                        (type !== ProviderTypeEnum.CustomOpenAI &&
+                          !providersFromStorage.has(type) &&
+                          !modifiedProviders.has(type)),
                     )
                     .map(type => (
                       <button
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 51249c14..d990daa7 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -118,25 +118,25 @@ importers:
         version: link:../packages/storage
       '@langchain/anthropic':
         specifier: ^0.3.12
-        version: 0.3.12(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))
+        version: 0.3.12(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))
       '@langchain/core':
         specifier: ^0.3.37
-        version: 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+        version: 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       '@langchain/deepseek':
         specifier: ^0.0.1
-        version: 0.0.1(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
+        version: 0.0.1(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
       '@langchain/google-genai':
         specifier: 0.1.11
-        version: 0.1.11(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(zod@3.24.1)
+        version: 0.1.11(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(zod@3.24.1)
       '@langchain/ollama':
         specifier: ^0.2.0
-        version: 0.2.0(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))
+        version: 0.2.0(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))
       '@langchain/openai':
         specifier: ^0.4.2
-        version: 0.4.2(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
+        version: 0.4.2(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
       '@langchain/xai':
         specifier: ^0.0.2
-        version: 0.0.2(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
+        version: 0.0.2(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
       puppeteer-core:
         specifier: 24.1.1
         version: 24.1.1
@@ -619,8 +619,8 @@ packages:
     peerDependencies:
       '@langchain/core': '>=0.2.21 <0.4.0'
 
-  '@langchain/core@0.3.37':
-    resolution: {integrity: sha512-LFk9GqHxcyCFx0oXvCBP7vDZIOUHYzzNU7JR+2ofIMnfkBLzcCKzBLySQDfPtd13PrpGHkaeOeLq8H1Tqi9lSw==}
+  '@langchain/core@0.3.44':
+    resolution: {integrity: sha512-3BsSFf7STvPPZyl2kMANgtVnCUvDdyP4k+koP+nY2Tczd5V+RFkuazIn/JOj/xxy/neZjr4PxFU4BFyF1aKXOA==}
     engines: {node: '>=18'}
 
   '@langchain/deepseek@0.0.1':
@@ -3465,17 +3465,17 @@ snapshots:
       '@jridgewell/resolve-uri': 3.1.2
       '@jridgewell/sourcemap-codec': 1.5.0
 
-  '@langchain/anthropic@0.3.12(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))':
+  '@langchain/anthropic@0.3.12(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))':
     dependencies:
       '@anthropic-ai/sdk': 0.32.1
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       fast-xml-parser: 4.5.0
       zod: 3.24.1
       zod-to-json-schema: 3.24.4(zod@3.24.1)
     transitivePeerDependencies:
       - encoding
 
-  '@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))':
+  '@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))':
     dependencies:
       '@cfworker/json-schema': 4.1.1
       ansi-styles: 5.2.0
@@ -3487,39 +3487,39 @@ snapshots:
       p-queue: 6.6.2
       p-retry: 4.6.2
       uuid: 10.0.0
-      zod: 3.24.1
-      zod-to-json-schema: 3.24.4(zod@3.24.1)
+      zod: 3.24.2
+      zod-to-json-schema: 3.24.4(zod@3.24.2)
     transitivePeerDependencies:
       - openai
 
-  '@langchain/deepseek@0.0.1(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
+  '@langchain/deepseek@0.0.1(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
     dependencies:
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
-      '@langchain/openai': 0.4.4(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/openai': 0.4.4(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
       zod: 3.24.2
     transitivePeerDependencies:
       - encoding
       - ws
 
-  '@langchain/google-genai@0.1.11(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(zod@3.24.1)':
+  '@langchain/google-genai@0.1.11(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(zod@3.24.1)':
     dependencies:
       '@google/generative-ai': 0.21.0
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       zod-to-json-schema: 3.24.4(zod@3.24.1)
     transitivePeerDependencies:
       - zod
 
-  '@langchain/ollama@0.2.0(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))':
+  '@langchain/ollama@0.2.0(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))':
     dependencies:
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       ollama: 0.5.14
       uuid: 10.0.0
       zod: 3.24.1
       zod-to-json-schema: 3.24.4(zod@3.24.1)
 
-  '@langchain/openai@0.4.2(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
+  '@langchain/openai@0.4.2(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
     dependencies:
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       js-tiktoken: 1.0.17
       openai: 4.82.0(ws@8.18.0)(zod@3.24.1)
       zod: 3.24.1
@@ -3528,9 +3528,9 @@ snapshots:
       - encoding
       - ws
 
-  '@langchain/openai@0.4.4(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
+  '@langchain/openai@0.4.4(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
     dependencies:
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
       js-tiktoken: 1.0.17
       openai: 4.82.0(ws@8.18.0)(zod@3.24.2)
       zod: 3.24.2
@@ -3539,10 +3539,10 @@ snapshots:
       - encoding
       - ws
 
-  '@langchain/xai@0.0.2(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
+  '@langchain/xai@0.0.2(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)':
     dependencies:
-      '@langchain/core': 0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
-      '@langchain/openai': 0.4.4(@langchain/core@0.3.37(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
+      '@langchain/core': 0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1))
+      '@langchain/openai': 0.4.4(@langchain/core@0.3.44(openai@4.82.0(ws@8.18.0)(zod@3.24.1)))(ws@8.18.0)
       zod: 3.24.2
     transitivePeerDependencies:
       - encoding
@@ -4086,7 +4086,7 @@ snapshots:
 
   ast-types@0.13.4:
     dependencies:
-      tslib: 2.7.0
+      tslib: 2.8.1
 
   asynckit@0.4.0: {}
 


Reviews:
user: login: alexchenzl
body: @bsormagec Thank you for your awesome work! Lots of users want the Azure support!

The following are my feedbacks after some tests:

1. Looks like users can not add more than one Azure OpenAI models in settings. 
2. For OpenRouter provider, I have ever thought of fetching models before, but I decide not to do it in the extension for these considerations:

   a. OpenRouter has too many models, it will make the UI/UX too complex
   
   b. A lot of OpenRouter models are not supported well. For example, openai/gpt-4o-2024-11-20 works well, but openai/gpt-4o not.
   
   c. Users of OpenRouter usually are developers, it's not difficult for them to add models manually.
   d. For a mid-term plan, I will add a backend API for these cloud LLM providers. It will provide a general interface to fetch default models, searching with filters and so on. It will also improve the simplicity of the frontend code in the extension.

3. I hope you could understand my considerations above, would you mind helping to make the following changes:

   a. Set several default models that work well with Nanobrowser in OpenRouter
   
   b. Remove fetching models from OpenRouter, just let users add modes manually if they are not in the default models
   
   c. Add custom headers to identify Nanobrowser in OpenRouter if possible. For your reference: https://github.com/OpenRouterTeam/openrouter-examples/blob/main/examples/langchain/index.ts#L25 
state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-12T03:52:14Z
user: login: alexchenzl
body: 1.    Looks like this part was removed:

// O series models have different parameters
  if (isOpenAIOModel(modelConfig.modelName)) {
    args.modelKwargs = {
      max_completion_tokens: maxTokens,
    };
  } else {
  }
  
 2. For Azure OpenAI config, model name is removed. Is there any way to know the model name so that we can apply different parameters like above ?
  

  

state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-18T07:43:59Z
user: login: alexchenzl
body: ![image](https://github.com/user-attachments/assets/78280e19-1ed5-4852-8c06-9423dcc61d66)
It's almost done, but I found some minor issues when adding Azure OpenAI provider:

1. There are two * appended to label Endpoint
2. The * appended to label Deployment is not displayed
3. "No deployment names set. Add model names below." Looks like  these words not necessary. The placeholder of input field should be enough


I also have a question:

What will happen if the deployment name is not a formal OpenAI model name? Will  a deployment name like _my-gpt-4o_ work ?






state: COMMENTED
author_association: COLLABORATOR
submitted_at: 2025-04-22T09:25:46Z
user: login: alexchenzl
body: Thank you for your awesome work!
state: APPROVED
author_association: COLLABORATOR
submitted_at: 2025-04-26T04:20:59Z

Review comments:
path: pages/side-panel/src/components/ChatHistoryList.tsx
user: login: alexchenzl
body: looks like this change is not necessary
created_at: 2025-04-12T03:10:39Z
author_association: COLLABORATOR
path: pages/side-panel/src/components/MessageList.tsx
user: login: alexchenzl
body: looks like this change is not necessary
created_at: 2025-04-12T03:11:07Z
author_association: COLLABORATOR

PR comments:
user: login: bsormagec
created_at: 2025-04-10T06:08:28Z
body: @alexchenzl waiting your comments on this :) 
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-12T03:56:40Z
body: Hi @alexchenzl, yeah, I can make those changes too. It takes a little time for me, but yeah, I can. I just needed it yesterday and just made it for myself and then shared it with the community. And I will try to do that your requests and mention you soon. 👍🏼
author_association: CONTRIBUTOR
user: login: alexchenzl
created_at: 2025-04-12T03:59:03Z
body: > Hi @alexchenzl, yeah, I can make those changes too. It takes a little time for me, but yeah, I can. I just needed it yesterday and just made it for myself and then shared it with the community. And I will try to do that your requests and mention you soon. 👍🏼

Really appreciate your great work and understanding!
author_association: COLLABORATOR
user: login: bsormagec
created_at: 2025-04-19T02:09:07Z
body: I am working to add it back on the new revision. 

> 1. Looks like this part was removed:
> 
> // O series models have different parameters if (isOpenAIOModel(modelConfig.modelName)) { args.modelKwargs = { max_completion_tokens: maxTokens, }; } else { }
> 
> 2. For Azure OpenAI config, model name is removed. Is there any way to know the model name so that we can apply different parameters like above ?


author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-19T05:05:20Z
body: Hi @alexchenzl, it looks like this is ready for merge! I’ve added some more features, especially for reasoning models, and updated all the flows as you suggested. Everything seems set for deployment now. I hope the community enjoys it—and I never say no to anyone who wants to thank me! :)

P.S. If you’re feeling extra grateful and want to leave a tip, you can do so on my GitHub Sponsors page. Your tip will not only make my day, but it might also make my coffee cup overflow with joy (and caffeine)! ☕💸
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-21T23:47:50Z
body: Can you check it @alexchenzl, i am waiting to use it 😅
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-22T14:36:07Z
body: 
> I also have a question:
> 
> What will happen if the deployment name is not a formal OpenAI model name? Will a deployment name like _my-gpt-4o_ work ?

Hi @alexchenzl 

By default, the model name appears as the deployment name in the Azure Panel. If it changes, the setup will likely not work. If you want to use it here, you should keep it the same; otherwise, you cannot use multiple models under a single endpoint because Azure creates one endpoint for each location. You can access all of them through the main domain by changing only the model information. Therefore, I don't think it's necessary to introduce this complexity into the application right now. If you want to use it here, use it in this way; otherwise, the code will become complex, and the clean code structure will be disrupted, requiring many if blocks. If you say we should switch to that format, then we can, but I think it's beneficial to keep it simple in this way. 


author_association: CONTRIBUTOR
user: login: alexchenzl
created_at: 2025-04-23T02:25:29Z
body: > > I also have a question:
> > What will happen if the deployment name is not a formal OpenAI model name? Will a deployment name like _my-gpt-4o_ work ?
> 
> Hi @alexchenzl
> 
> By default, the model name appears as the deployment name in the Azure Panel. If it changes, the setup will likely not work. If you want to use it here, you should keep it the same; otherwise, you cannot use multiple models under a single endpoint because Azure creates one endpoint for each location. You can access all of them through the main domain by changing only the model information. Therefore, I don't think it's necessary to introduce this complexity into the application right now. If you want to use it here, use it in this way; otherwise, the code will become complex, and the clean code structure will be disrupted, requiring many if blocks. If you say we should switch to that format, then we can, but I think it's beneficial to keep it simple in this way.

I agree with that we should not introduce too much complexity here.  But we can not prevent users from changing default deployment names, then they will encounter issues but don't know the reason.

I suggest to add some notes below the deployment input field or in the input placeholder,  in order to let users know that they should keep the deployment name same as the model name. Then they will know the reason when they encounter issues.



author_association: COLLABORATOR
user: login: bsormagec
created_at: 2025-04-23T21:00:33Z
body: > > > I also have a question:
> > > What will happen if the deployment name is not a formal OpenAI model name? Will a deployment name like _my-gpt-4o_ work ?
> > 
> > 
> > Hi @alexchenzl
> > By default, the model name appears as the deployment name in the Azure Panel. If it changes, the setup will likely not work. If you want to use it here, you should keep it the same; otherwise, you cannot use multiple models under a single endpoint because Azure creates one endpoint for each location. You can access all of them through the main domain by changing only the model information. Therefore, I don't think it's necessary to introduce this complexity into the application right now. If you want to use it here, use it in this way; otherwise, the code will become complex, and the clean code structure will be disrupted, requiring many if blocks. If you say we should switch to that format, then we can, but I think it's beneficial to keep it simple in this way.
> 
> I agree with that we should not introduce too much complexity here. But we can not prevent users from changing default deployment names, then they will encounter issues but don't know the reason.
> 
> I suggest to add some notes below the deployment input field or in the input placeholder, in order to let users know that they should keep the deployment name same as the model name. Then they will know the reason when they encounter issues.

OK, I will try to do it.
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-24T18:30:56Z
body: Hi @alexchenzl, 
I implemented all the changes you requested and added "Azure provider handling to support multiple instances" to facilitate those who need to utilize different locations through the model or deployment. I hope you review it as soon as possible and merge those changes. 
author_association: CONTRIBUTOR
user: login: bsormagec
created_at: 2025-04-26T04:23:09Z
body: I hope everyone loves it! 🤘🏼 @alexchenzl 
author_association: CONTRIBUTOR