@@ -15,6 +15,7 @@ export type CopilotByokProxyHandle = {
1515} ;
1616
1717type HeaderValue = string | number | string [ ] | undefined ;
18+ type ProviderConfig = NonNullable < ResolvedCopilotProvider [ "provider" ] > ;
1819
1920export async function createCopilotByokProxy (
2021 resolvedProvider : ResolvedCopilotProvider ,
@@ -32,6 +33,7 @@ export async function createCopilotByokProxy(
3233 const targetPathPrefix = trimTrailingSlash ( targetBaseUrl . pathname ) ;
3334 const proxyPathPrefix = `/${ nonce } ${ targetPathPrefix } ` ;
3435 const acceptsAzureSdkPaths = providerConfig . type === "azure" ;
36+ const upstreamBearerAuthorization = resolveUpstreamBearerAuthorization ( providerConfig ) ;
3537 const activeFetches = new Set < AbortController > ( ) ;
3638 const server = createServer ( ( req , res ) => {
3739 void handleProxyRequest ( req , res , {
@@ -40,6 +42,7 @@ export async function createCopilotByokProxy(
4042 proxyPathPrefix,
4143 targetBaseUrl,
4244 targetPathPrefix,
45+ upstreamBearerAuthorization,
4346 } ) ;
4447 } ) ;
4548
@@ -88,6 +91,7 @@ async function handleProxyRequest(
8891 proxyPathPrefix : string ;
8992 targetBaseUrl : URL ;
9093 targetPathPrefix : string ;
94+ upstreamBearerAuthorization : string | undefined ;
9195 } ,
9296) : Promise < void > {
9397 let guarded : Awaited < ReturnType < typeof fetchWithSsrFGuard > > | undefined ;
@@ -101,6 +105,7 @@ async function handleProxyRequest(
101105 }
102106 } ) ;
103107 try {
108+ const canInjectBearerAuthorization = isNonceProtectedProxyRequest ( req , params . proxyPathPrefix ) ;
104109 const url = resolveTargetUrl ( req , params ) ;
105110 if ( ! url ) {
106111 res . writeHead ( 404 ) ;
@@ -112,7 +117,11 @@ async function handleProxyRequest(
112117 url : url . toString ( ) ,
113118 init : {
114119 method : req . method ,
115- headers : normalizeProxyRequestHeaders ( req . headers ) ,
120+ headers : buildProxyRequestHeaders ( req . headers , {
121+ upstreamBearerAuthorization : canInjectBearerAuthorization
122+ ? params . upstreamBearerAuthorization
123+ : undefined ,
124+ } ) ,
116125 signal : upstreamAbort . signal ,
117126 ...( body ? { body : toFetchBody ( body ) } : { } ) ,
118127 } ,
@@ -129,9 +138,9 @@ async function handleProxyRequest(
129138 return ;
130139 }
131140 await finished (
132- Readable . fromWeb (
133- guarded . response . body as unknown as NodeReadableStream < Uint8Array > ,
134- ) . pipe ( res ) ,
141+ Readable . fromWeb ( guarded . response . body as unknown as NodeReadableStream < Uint8Array > ) . pipe (
142+ res ,
143+ ) ,
135144 ) ;
136145 } catch ( error ) {
137146 if ( res . destroyed || res . writableEnded ) {
@@ -190,6 +199,14 @@ function isAzureSdkProxyPath(pathname: string): boolean {
190199 return pathname === "/openai" || pathname . startsWith ( "/openai/" ) ;
191200}
192201
202+ function isNonceProtectedProxyRequest ( req : IncomingMessage , proxyPathPrefix : string ) : boolean {
203+ const incomingUrl = new URL ( req . url ?? "/" , `http://${ LOOPBACK_HOST } ` ) ;
204+ return (
205+ incomingUrl . pathname === proxyPathPrefix ||
206+ incomingUrl . pathname . startsWith ( `${ proxyPathPrefix } /` )
207+ ) ;
208+ }
209+
193210async function readBody ( req : IncomingMessage ) : Promise < Buffer | undefined > {
194211 const chunks : Buffer [ ] = [ ] ;
195212 for await ( const chunk of req ) {
@@ -219,6 +236,25 @@ function normalizeProxyRequestHeaders(headers: IncomingMessage["headers"]): Reco
219236 return out ;
220237}
221238
239+ function buildProxyRequestHeaders (
240+ headers : IncomingMessage [ "headers" ] ,
241+ params : { upstreamBearerAuthorization : string | undefined } ,
242+ ) : Record < string , string > {
243+ const out = normalizeProxyRequestHeaders ( headers ) ;
244+ if ( params . upstreamBearerAuthorization && ! hasHeader ( out , "authorization" ) ) {
245+ // The SDK declares bearerToken as Authorization auth, but some BYOK
246+ // adapter paths can omit it before reaching our loopback proxy. The proxy
247+ // owns the final guarded hop, so inject only when the SDK left it absent.
248+ out [ "authorization" ] = params . upstreamBearerAuthorization ;
249+ }
250+ return out ;
251+ }
252+
253+ function resolveUpstreamBearerAuthorization ( providerConfig : ProviderConfig ) : string | undefined {
254+ const bearerToken = providerConfig . bearerToken ?. trim ( ) ;
255+ return bearerToken ? `Bearer ${ bearerToken } ` : undefined ;
256+ }
257+
222258function normalizeProxyResponseHeaders ( headers : Headers ) : Record < string , string > {
223259 const out : Record < string , string > = { } ;
224260 headers . forEach ( ( value , key ) => {
@@ -236,6 +272,10 @@ function normalizeHeaderValue(value: HeaderValue): string | undefined {
236272 return Array . isArray ( value ) ? value . join ( ", " ) : String ( value ) ;
237273}
238274
275+ function hasHeader ( headers : Record < string , string > , target : string ) : boolean {
276+ return Object . keys ( headers ) . some ( ( key ) => key . toLowerCase ( ) === target ) ;
277+ }
278+
239279function isHopByHopHeader ( key : string ) : boolean {
240280 switch ( key . toLowerCase ( ) ) {
241281 case "connection" :
0 commit comments