From c920fc143328a0a7d509a7c6a2028e700610d492 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Thu, 22 Jan 2026 20:51:09 -0800 Subject: [PATCH 1/8] Fix: Load dotenv in agent.ts before AgentApplication instantiates --- nodejs/openai/sample-agent/src/agent.ts | 5 +++++ nodejs/openai/sample-agent/src/index.ts | 5 +---- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/nodejs/openai/sample-agent/src/agent.ts b/nodejs/openai/sample-agent/src/agent.ts index f215cd74..2fec4d9f 100644 --- a/nodejs/openai/sample-agent/src/agent.ts +++ b/nodejs/openai/sample-agent/src/agent.ts @@ -1,6 +1,11 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. +// IMPORTANT: Load environment variables FIRST before any other imports +// This ensures NODE_ENV and other config is available when AgentApplication initializes +import { configDotenv } from 'dotenv'; +configDotenv(); + import { TurnState, AgentApplication, TurnContext, MemoryStorage } from '@microsoft/agents-hosting'; import { ActivityTypes } from '@microsoft/agents-activity'; import { BaggageBuilder } from '@microsoft/agents-a365-observability'; diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index db3d1e55..6c261bdb 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -1,10 +1,7 @@ // Copyright (c) Microsoft Corporation. // Licensed under the MIT License. -// It is important to load environment variables before importing other modules -import { configDotenv } from 'dotenv'; - -configDotenv(); +// Note: dotenv is loaded in agent.ts before AgentApplication is instantiated import { AuthConfiguration, authorizeJWT, CloudAdapter, loadAuthConfigFromEnv, Request } from '@microsoft/agents-hosting'; import express, { Response } from 'express' From 74a2b1590a88e30a1eacbefb14eb83acc799a5ae Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Thu, 22 Jan 2026 22:43:43 -0800 Subject: [PATCH 2/8] Add /api/health endpoint and always bind to 0.0.0.0 --- nodejs/openai/sample-agent/src/index.ts | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index 6c261bdb..902b8cc5 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -13,6 +13,16 @@ const authConfig: AuthConfiguration = isProduction ? loadAuthConfigFromEnv() : { const server = express() server.use(express.json()) + +// Health endpoint - placed BEFORE auth middleware so it doesn't require authentication +server.get('/api/health', (req, res: Response) => { + res.status(200).json({ + status: 'healthy', + timestamp: new Date().toISOString(), + appId: authConfig.clientId || 'not-configured' + }); +}); + server.use(authorizeJWT(authConfig)) server.post('/api/messages', (req: Request, res: Response) => { @@ -23,7 +33,8 @@ server.post('/api/messages', (req: Request, res: Response) => { }) const port = Number(process.env.PORT) || 3978 -const host = isProduction ? '0.0.0.0' : '127.0.0.1'; +// Always bind to 0.0.0.0 to allow external connections (needed for E2E tests and Azure) +const host = '0.0.0.0'; server.listen(port, host, async () => { console.log(`\nServer listening on ${host}:${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`) }).on('error', async (err: unknown) => { From aafeaa8ea4ad9b1ace5bd2b39352f667e9926e12 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Thu, 22 Jan 2026 22:58:44 -0800 Subject: [PATCH 3/8] Fix: NODE_ENV=development takes priority over WEBSITE_SITE_NAME for auth bypass --- nodejs/openai/sample-agent/src/index.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index 902b8cc5..d06fd919 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -7,10 +7,14 @@ import { AuthConfiguration, authorizeJWT, CloudAdapter, loadAuthConfigFromEnv, R import express, { Response } from 'express' import { agentApplication } from './agent'; -// Use request validation middleware only if hosting publicly -const isProduction = Boolean(process.env.WEBSITE_SITE_NAME) || process.env.NODE_ENV === 'production'; +// Development mode explicitly overrides production detection +// NODE_ENV=development takes priority over WEBSITE_SITE_NAME +const isDevelopment = process.env.NODE_ENV === 'development'; +const isProduction = !isDevelopment && (Boolean(process.env.WEBSITE_SITE_NAME) || process.env.NODE_ENV === 'production'); const authConfig: AuthConfiguration = isProduction ? loadAuthConfigFromEnv() : {}; +console.log(`Environment: NODE_ENV=${process.env.NODE_ENV}, isProduction=${isProduction}, isDevelopment=${isDevelopment}`); + const server = express() server.use(express.json()) From 2aacff70ddfc70d8fae6c517c2ee80fb0c344d41 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Thu, 22 Jan 2026 23:45:12 -0800 Subject: [PATCH 4/8] feat: Add Azure OpenAI support to Node.js OpenAI sample - Add openai-config.ts for conditional OpenAI/Azure OpenAI configuration - Use setDefaultOpenAIClient with AzureOpenAI for Azure endpoints - Use chat_completions API for Azure OpenAI compatibility - Update .env.template with Azure OpenAI environment variables - Dynamic model/deployment name selection based on provider --- nodejs/openai/sample-agent/.env.template | 10 +++ nodejs/openai/sample-agent/src/client.ts | 10 +++ .../openai/sample-agent/src/openai-config.ts | 67 +++++++++++++++++++ 3 files changed, 87 insertions(+) create mode 100644 nodejs/openai/sample-agent/src/openai-config.ts diff --git a/nodejs/openai/sample-agent/.env.template b/nodejs/openai/sample-agent/.env.template index bcd44e95..b96b68e7 100644 --- a/nodejs/openai/sample-agent/.env.template +++ b/nodejs/openai/sample-agent/.env.template @@ -1,5 +1,15 @@ # OpenAI Configuration +# Use EITHER standard OpenAI OR Azure OpenAI (not both) + +# Option 1: Standard OpenAI API OPENAI_API_KEY= +OPENAI_MODEL=gpt-4o + +# Option 2: Azure OpenAI (takes precedence if AZURE_OPENAI_API_KEY is set) +AZURE_OPENAI_API_KEY= +AZURE_OPENAI_ENDPOINT= +AZURE_OPENAI_DEPLOYMENT= +AZURE_OPENAI_API_VERSION=2024-10-21 # MCP Tooling Configuration BEARER_TOKEN= diff --git a/nodejs/openai/sample-agent/src/client.ts b/nodejs/openai/sample-agent/src/client.ts index 21792232..56e05908 100644 --- a/nodejs/openai/sample-agent/src/client.ts +++ b/nodejs/openai/sample-agent/src/client.ts @@ -7,6 +7,9 @@ import { Authorization, TurnContext } from '@microsoft/agents-hosting'; import { McpToolRegistrationService } from '@microsoft/agents-a365-tooling-extensions-openai'; import { AgenticTokenCacheInstance} from '@microsoft/agents-a365-observability-hosting' +// OpenAI/Azure OpenAI Configuration +import { configureOpenAIClient, getModelName, isAzureOpenAI } from './openai-config'; + // Observability Imports import { ObservabilityManager, @@ -21,6 +24,9 @@ import { import { OpenAIAgentsTraceInstrumentor } from '@microsoft/agents-a365-observability-extensions-openai'; import { tokenResolver } from './token-cache'; +// Configure OpenAI/Azure OpenAI client before any agent operations +configureOpenAIClient(); + export interface Client { invokeAgentWithScope(prompt: string): Promise; } @@ -58,9 +64,13 @@ openAIAgentsTraceInstrumentor.enable(); const toolService = new McpToolRegistrationService(); export async function getClient(authorization: Authorization, authHandlerName: string, turnContext: TurnContext): Promise { + const modelName = getModelName(); + console.log(`[Client] Creating agent with model: ${modelName} (Azure: ${isAzureOpenAI()})`); + const agent = new Agent({ // You can customize the agent configuration here if needed name: 'OpenAI Agent', + model: modelName, instructions: `You are a helpful assistant with access to tools provided by MCP (Model Context Protocol) servers. When users ask about your MCP servers, tools, or capabilities, use introspection to list the tools you have available. You can see all the tools registered to you and should report them accurately when asked. diff --git a/nodejs/openai/sample-agent/src/openai-config.ts b/nodejs/openai/sample-agent/src/openai-config.ts new file mode 100644 index 00000000..4a510d46 --- /dev/null +++ b/nodejs/openai/sample-agent/src/openai-config.ts @@ -0,0 +1,67 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +/** + * OpenAI/Azure OpenAI Configuration + * + * This module configures the OpenAI SDK to work with either: + * - Standard OpenAI API (using OPENAI_API_KEY) + * - Azure OpenAI (using AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT) + * + * Azure OpenAI takes precedence if AZURE_OPENAI_API_KEY is set. + */ + +import { AzureOpenAI, OpenAI } from 'openai'; +import { setDefaultOpenAIClient, setOpenAIAPI } from '@openai/agents'; + +/** + * Determines if Azure OpenAI should be used based on environment variables. + */ +export function isAzureOpenAI(): boolean { + return Boolean(process.env.AZURE_OPENAI_API_KEY && process.env.AZURE_OPENAI_ENDPOINT); +} + +/** + * Gets the model/deployment name to use. + * For Azure OpenAI, this is the deployment name. + * For standard OpenAI, this is the model name. + */ +export function getModelName(): string { + if (isAzureOpenAI()) { + return process.env.AZURE_OPENAI_DEPLOYMENT || 'gpt-4o'; + } + return process.env.OPENAI_MODEL || 'gpt-4o'; +} + +/** + * Configures the OpenAI SDK with the appropriate client. + * Call this function early in your application startup. + */ +export function configureOpenAIClient(): void { + if (isAzureOpenAI()) { + console.log('[OpenAI Config] Using Azure OpenAI'); + console.log(`[OpenAI Config] Endpoint: ${process.env.AZURE_OPENAI_ENDPOINT}`); + console.log(`[OpenAI Config] Deployment: ${process.env.AZURE_OPENAI_DEPLOYMENT}`); + + const azureClient = new AzureOpenAI({ + apiKey: process.env.AZURE_OPENAI_API_KEY, + endpoint: process.env.AZURE_OPENAI_ENDPOINT, + apiVersion: process.env.AZURE_OPENAI_API_VERSION || '2024-10-21', + deployment: process.env.AZURE_OPENAI_DEPLOYMENT, + }); + + // Set the Azure client as the default for @openai/agents + setDefaultOpenAIClient(azureClient as unknown as OpenAI); + + // Azure OpenAI requires Chat Completions API (not Responses API) + setOpenAIAPI('chat_completions'); + } else if (process.env.OPENAI_API_KEY) { + console.log('[OpenAI Config] Using standard OpenAI API'); + // Standard OpenAI uses OPENAI_API_KEY automatically + // No need to set client explicitly + } else { + console.warn('[OpenAI Config] WARNING: No OpenAI or Azure OpenAI credentials found!'); + console.warn('[OpenAI Config] Set OPENAI_API_KEY for standard OpenAI'); + console.warn('[OpenAI Config] Or set AZURE_OPENAI_API_KEY, AZURE_OPENAI_ENDPOINT, AZURE_OPENAI_DEPLOYMENT for Azure OpenAI'); + } +} From 393336be196a21446956b6173df7dbe016128f1f Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Fri, 23 Jan 2026 00:43:15 -0800 Subject: [PATCH 5/8] fix host --- nodejs/openai/sample-agent/src/index.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index d06fd919..d2805019 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -37,8 +37,7 @@ server.post('/api/messages', (req: Request, res: Response) => { }) const port = Number(process.env.PORT) || 3978 -// Always bind to 0.0.0.0 to allow external connections (needed for E2E tests and Azure) -const host = '0.0.0.0'; +const host = process.env.HOST ?? (isProduction ? '0.0.0.0' : 'localhost'); server.listen(port, host, async () => { console.log(`\nServer listening on ${host}:${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`) }).on('error', async (err: unknown) => { From 7bbee3a4a673d7ffb311b7e9e807ed6d138af287 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Fri, 23 Jan 2026 00:52:54 -0800 Subject: [PATCH 6/8] fix: Address Copilot review comments - Add 'openai' as direct dependency in package.json - Check AZURE_OPENAI_DEPLOYMENT in isAzureOpenAI() function - Throw clear error if AZURE_OPENAI_DEPLOYMENT is missing - Remove appId from health endpoint to reduce information disclosure --- nodejs/openai/sample-agent/package.json | 1 + nodejs/openai/sample-agent/src/index.ts | 3 +-- nodejs/openai/sample-agent/src/openai-config.ts | 15 ++++++++++++--- 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/nodejs/openai/sample-agent/package.json b/nodejs/openai/sample-agent/package.json index 54c778f0..391cf1d7 100644 --- a/nodejs/openai/sample-agent/package.json +++ b/nodejs/openai/sample-agent/package.json @@ -26,6 +26,7 @@ "@microsoft/agents-a365-observability-extensions-openai": "^0.1.0-preview.30", "@openai/agents": "0.1.11", "dotenv": "^17.2.2", + "openai": "^4.77.0", "express": "^5.1.0" }, "devDependencies": { diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index d2805019..ef29c90f 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -22,8 +22,7 @@ server.use(express.json()) server.get('/api/health', (req, res: Response) => { res.status(200).json({ status: 'healthy', - timestamp: new Date().toISOString(), - appId: authConfig.clientId || 'not-configured' + timestamp: new Date().toISOString() }); }); diff --git a/nodejs/openai/sample-agent/src/openai-config.ts b/nodejs/openai/sample-agent/src/openai-config.ts index 4a510d46..bb2b84ba 100644 --- a/nodejs/openai/sample-agent/src/openai-config.ts +++ b/nodejs/openai/sample-agent/src/openai-config.ts @@ -16,19 +16,28 @@ import { setDefaultOpenAIClient, setOpenAIAPI } from '@openai/agents'; /** * Determines if Azure OpenAI should be used based on environment variables. + * All three variables (API_KEY, ENDPOINT, DEPLOYMENT) must be set. */ export function isAzureOpenAI(): boolean { - return Boolean(process.env.AZURE_OPENAI_API_KEY && process.env.AZURE_OPENAI_ENDPOINT); + return Boolean( + process.env.AZURE_OPENAI_API_KEY && + process.env.AZURE_OPENAI_ENDPOINT && + process.env.AZURE_OPENAI_DEPLOYMENT + ); } /** * Gets the model/deployment name to use. - * For Azure OpenAI, this is the deployment name. + * For Azure OpenAI, this is the deployment name (required). * For standard OpenAI, this is the model name. */ export function getModelName(): string { if (isAzureOpenAI()) { - return process.env.AZURE_OPENAI_DEPLOYMENT || 'gpt-4o'; + const deployment = process.env.AZURE_OPENAI_DEPLOYMENT; + if (!deployment) { + throw new Error('AZURE_OPENAI_DEPLOYMENT is required when using Azure OpenAI'); + } + return deployment; } return process.env.OPENAI_MODEL || 'gpt-4o'; } From 7e5dcb349e9447b24144907da1ee635116eb3a89 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Fri, 23 Jan 2026 09:39:38 -0800 Subject: [PATCH 7/8] fix: Resolve OpenAI type version mismatch - Remove direct openai dependency (use transitive from @openai/agents) - Use require() and 'any' type to bypass version compatibility issues --- nodejs/openai/sample-agent/package.json | 1 - nodejs/openai/sample-agent/src/openai-config.ts | 8 ++++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/nodejs/openai/sample-agent/package.json b/nodejs/openai/sample-agent/package.json index 391cf1d7..54c778f0 100644 --- a/nodejs/openai/sample-agent/package.json +++ b/nodejs/openai/sample-agent/package.json @@ -26,7 +26,6 @@ "@microsoft/agents-a365-observability-extensions-openai": "^0.1.0-preview.30", "@openai/agents": "0.1.11", "dotenv": "^17.2.2", - "openai": "^4.77.0", "express": "^5.1.0" }, "devDependencies": { diff --git a/nodejs/openai/sample-agent/src/openai-config.ts b/nodejs/openai/sample-agent/src/openai-config.ts index bb2b84ba..20fa1c75 100644 --- a/nodejs/openai/sample-agent/src/openai-config.ts +++ b/nodejs/openai/sample-agent/src/openai-config.ts @@ -11,7 +11,9 @@ * Azure OpenAI takes precedence if AZURE_OPENAI_API_KEY is set. */ -import { AzureOpenAI, OpenAI } from 'openai'; +// Note: We import AzureOpenAI from 'openai' which is a transitive dependency of @openai/agents +// eslint-disable-next-line @typescript-eslint/no-require-imports +const { AzureOpenAI } = require('openai'); import { setDefaultOpenAIClient, setOpenAIAPI } from '@openai/agents'; /** @@ -60,7 +62,9 @@ export function configureOpenAIClient(): void { }); // Set the Azure client as the default for @openai/agents - setDefaultOpenAIClient(azureClient as unknown as OpenAI); + // Using 'any' to bypass type version mismatch between openai package versions + // eslint-disable-next-line @typescript-eslint/no-explicit-any + setDefaultOpenAIClient(azureClient as any); // Azure OpenAI requires Chat Completions API (not Responses API) setOpenAIAPI('chat_completions'); From cc3f10fbafc8e81360bac7a2735f005748a88157 Mon Sep 17 00:00:00 2001 From: Rahul Devikar Date: Fri, 23 Jan 2026 09:57:27 -0800 Subject: [PATCH 8/8] fix: Simplify environment detection logic - Remove isProduction variable, use only isDevelopment - Auth is required for everything except NODE_ENV=development - Addresses pontemonti's review comment about ambiguous state --- nodejs/openai/sample-agent/src/index.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/nodejs/openai/sample-agent/src/index.ts b/nodejs/openai/sample-agent/src/index.ts index ef29c90f..9977ff36 100644 --- a/nodejs/openai/sample-agent/src/index.ts +++ b/nodejs/openai/sample-agent/src/index.ts @@ -7,13 +7,12 @@ import { AuthConfiguration, authorizeJWT, CloudAdapter, loadAuthConfigFromEnv, R import express, { Response } from 'express' import { agentApplication } from './agent'; -// Development mode explicitly overrides production detection -// NODE_ENV=development takes priority over WEBSITE_SITE_NAME +// Only NODE_ENV=development explicitly disables authentication +// All other cases (production, test, unset, etc.) require authentication const isDevelopment = process.env.NODE_ENV === 'development'; -const isProduction = !isDevelopment && (Boolean(process.env.WEBSITE_SITE_NAME) || process.env.NODE_ENV === 'production'); -const authConfig: AuthConfiguration = isProduction ? loadAuthConfigFromEnv() : {}; +const authConfig: AuthConfiguration = isDevelopment ? {} : loadAuthConfigFromEnv(); -console.log(`Environment: NODE_ENV=${process.env.NODE_ENV}, isProduction=${isProduction}, isDevelopment=${isDevelopment}`); +console.log(`Environment: NODE_ENV=${process.env.NODE_ENV}, isDevelopment=${isDevelopment}`); const server = express() server.use(express.json()) @@ -36,7 +35,8 @@ server.post('/api/messages', (req: Request, res: Response) => { }) const port = Number(process.env.PORT) || 3978 -const host = process.env.HOST ?? (isProduction ? '0.0.0.0' : 'localhost'); +// Host is configurable; default to localhost for development, 0.0.0.0 for everything else +const host = process.env.HOST ?? (isDevelopment ? 'localhost' : '0.0.0.0'); server.listen(port, host, async () => { console.log(`\nServer listening on ${host}:${port} for appId ${authConfig.clientId} debug ${process.env.DEBUG}`) }).on('error', async (err: unknown) => {