A Node.js console application (written in TypeScript) that calls an OpenAI-compatible Chat Completions API and streams the response token-by-token to the console.
It is configured for the DNV Chat API (https://test.ai.api.dnv.com), which is
protected by Azure API Management and requires:
- An Azure AD (Microsoft Entra ID) bearer token obtained via the client credentials flow.
- Two custom gateway headers:
agentidandOcp-Apim-Subscription-Key.
- Calls any OpenAI-compatible Chat Completions endpoint via a configurable base URL.
- Acquires and caches an Azure AD access token automatically (refreshes before expiry).
- Injects
Authorization: Bearer <token>,agentid, andOcp-Apim-Subscription-Keyheaders on every request via a customfetchwrapper. - Streams the response using Server-Sent Events and prints tokens as they arrive.
- Optional corporate proxy support via the
HTTPS_PROXYenvironment variable. - Built-in diagnostics that decode the JWT and probe the API gateway on failure.
- All secrets and endpoint configuration live in a
.envfile.
- Node.js 18+ (uses the global
fetchAPI) - npm
-
Install dependencies:
npm install
-
Configure your environment. Copy the example and fill in your values:
cp .env.example .env
Then edit
.env:# Base URL (the SDK appends "/chat/completions") API_ENDPOINT=https://test.ai.api.dnv.com/chatdnvapi/v1 MODEL=chatdnv SYSTEM_PROMPT=You are a helpful assistant. # Custom gateway headers AGENT_ID=your-agent-id OCP_APIM_SUBSCRIPTION_KEY=your-subscription-key # Azure AD client credentials AZURE_AD_TENANT_ID=your-tenant-id AZURE_AD_CLIENT_ID=your-client-id AZURE_AD_CLIENT_SECRET=your-client-secret AZURE_AD_SCOPE=api://your-api-app-client-id/.default
About
AZURE_AD_SCOPE: This must be the backend API's Application ID URI +/.default. The Azure API Managementvalidate-jwtpolicy checks the token'saud(audience) claim against this value. If you getAADSTS500011: invalid_resourceor a401from the gateway, the scope is wrong — ask the API owner for the correct Application ID URI.
npm run devnpm run build
npm startPass the prompt as command-line arguments:
npm run dev -- "Explain streaming APIs in two sentences."
# or after building:
npm start -- "Write a haiku about TypeScript."If no prompt is provided, a friendly default is used.
.
├── .env # Local secrets (gitignored)
├── .env.example # Template for .env (tracked)
├── .gitignore
├── package.json
├── tsconfig.json
├── README.md
└── src
├── index.ts # Application entry point
├── tokenProvider.ts # Azure AD token acquisition & caching
├── diagnostics.ts # Connectivity & JWT diagnostics on failure
└── discover-apps.ts # Helper: list Entra ID service principals (requires Graph permissions)