@@ -8,13 +8,14 @@ import type {
88 StreamEvent ,
99 UsageInfo ,
1010} from "../core/model" ;
11- import type { ChatMessage , ToolCall , ToolDefinition } from "../core/types" ;
11+ import type { ChatMessage , HostedToolDefinition , ToolCall , ToolDefinition } from "../core/types" ;
1212import { resolveChatCompletionsUrl } from "./endpoint" ;
1313import { parseSSE } from "./sse-parser" ;
1414
1515export interface AzureChatCompletionsModelConfig {
1616 endpoint : string ;
17- apiKey : string ;
17+ apiKey ?: string ;
18+ azureAdTokenProvider ?: ( ) => Promise < string > ;
1819 deployment : string ;
1920 apiVersion ?: string ;
2021}
@@ -23,11 +24,23 @@ const DEFAULT_API_VERSION = "2025-03-01-preview";
2324
2425export class AzureChatCompletionsModel implements Model {
2526 private readonly url : string ;
26- private readonly apiKey : string ;
27+ private readonly apiKey ?: string ;
28+ private readonly tokenProvider ?: ( ) => Promise < string > ;
2729 private readonly deployment : string ;
2830
2931 constructor ( config : AzureChatCompletionsModelConfig ) {
32+ if ( config . apiKey && config . azureAdTokenProvider ) {
33+ throw new StratusError (
34+ "Provide either apiKey or azureAdTokenProvider, not both" ,
35+ ) ;
36+ }
37+ if ( ! config . apiKey && ! config . azureAdTokenProvider ) {
38+ throw new StratusError (
39+ "Provide either apiKey or azureAdTokenProvider" ,
40+ ) ;
41+ }
3042 this . apiKey = config . apiKey ;
43+ this . tokenProvider = config . azureAdTokenProvider ;
3144 this . deployment = config . deployment ;
3245 this . url = resolveChatCompletionsUrl (
3346 config . endpoint ,
@@ -36,6 +49,14 @@ export class AzureChatCompletionsModel implements Model {
3649 ) ;
3750 }
3851
52+ private async getAuthHeaders ( ) : Promise < Record < string , string > > {
53+ if ( this . tokenProvider ) {
54+ const token = await this . tokenProvider ( ) ;
55+ return { Authorization : `Bearer ${ token } ` } ;
56+ }
57+ return { "api-key" : this . apiKey ! } ;
58+ }
59+
3960 async getResponse (
4061 request : ModelRequest ,
4162 options ?: ModelRequestOptions ,
@@ -161,14 +182,8 @@ export class AzureChatCompletionsModel implements Model {
161182 }
162183
163184 if ( request . tools && request . tools . length > 0 ) {
164- for ( const tool of request . tools ) {
165- if ( ! ( "function" in tool ) ) {
166- throw new StratusError (
167- "Hosted tools (web_search, code_interpreter, mcp, image_generation) are not supported by the Chat Completions API. Use AzureResponsesModel instead." ,
168- ) ;
169- }
170- }
171- body . tools = request . tools as ToolDefinition [ ] ;
185+ assertAllFunctionTools ( request . tools ) ;
186+ body . tools = request . tools ;
172187 }
173188
174189 if ( request . responseFormat ) {
@@ -201,11 +216,12 @@ export class AzureChatCompletionsModel implements Model {
201216 ) : Promise < Response > {
202217 const maxRetries = 3 ;
203218 for ( let attempt = 0 ; attempt <= maxRetries ; attempt ++ ) {
219+ const authHeaders = await this . getAuthHeaders ( ) ;
204220 const response = await fetch ( this . url , {
205221 method : "POST" ,
206222 headers : {
207223 "Content-Type" : "application/json" ,
208- "api-key" : this . apiKey ,
224+ ... authHeaders ,
209225 } ,
210226 body : JSON . stringify ( body ) ,
211227 signal,
@@ -300,6 +316,18 @@ export class AzureChatCompletionsModel implements Model {
300316 }
301317}
302318
319+ function assertAllFunctionTools (
320+ tools : ( ToolDefinition | HostedToolDefinition ) [ ] ,
321+ ) : asserts tools is ToolDefinition [ ] {
322+ for ( const tool of tools ) {
323+ if ( ! ( "function" in tool ) ) {
324+ throw new StratusError (
325+ "Hosted tools (web_search, code_interpreter, mcp, image_generation) are not supported by the Chat Completions API. Use AzureResponsesModel instead." ,
326+ ) ;
327+ }
328+ }
329+ }
330+
303331function serializeMessage ( msg : ChatMessage ) : Record < string , unknown > {
304332 switch ( msg . role ) {
305333 case "system" :
0 commit comments