@@ -26,11 +26,19 @@ interface OpenAIEmbeddingResponse {
2626 * OpenAI Compatible implementation of the embedder interface with batching and rate limiting.
2727 * This embedder allows using any OpenAI-compatible API endpoint by specifying a custom baseURL.
2828 */
29+ interface HttpError extends Error {
30+ status ?: number
31+ response ?: {
32+ status ?: number
33+ }
34+ }
35+
2936export class OpenAICompatibleEmbedder implements IEmbedder {
3037 private embeddingsClient : OpenAI
3138 private readonly defaultModelId : string
3239 private readonly baseUrl : string
3340 private readonly apiKey : string
41+ private readonly isFullUrl : boolean
3442
3543 /**
3644 * Creates a new OpenAI Compatible embedder
@@ -53,6 +61,8 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
5361 apiKey : apiKey ,
5462 } )
5563 this . defaultModelId = modelId || getDefaultModelId ( "openai-compatible" )
64+ // Cache the URL type check for performance
65+ this . isFullUrl = this . isFullEndpointUrl ( baseUrl )
5666 }
5767
5868 /**
@@ -114,14 +124,23 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
114124 }
115125
116126 /**
117- * Determines if the provided URL is a full endpoint URL (contains /embeddings or /deployments/)
118- * or a base URL that needs the endpoint appended by the SDK
127+ * Determines if the provided URL is a full endpoint URL or a base URL that needs the endpoint appended by the SDK.
128+ * Uses smart pattern matching for known providers while accepting we can't cover all possible patterns.
119129 * @param url The URL to check
120130 * @returns true if it's a full endpoint URL, false if it's a base URL
121131 */
122132 private isFullEndpointUrl ( url : string ) : boolean {
123- // Check if the URL contains common embedding endpoint patterns
124- return url . includes ( "/embeddings" ) || url . includes ( "/deployments/" )
133+ // Known patterns for major providers
134+ const patterns = [
135+ // Azure OpenAI: /deployments/{deployment-name}/embeddings
136+ / \/ d e p l o y m e n t s \/ [ ^ \/ ] + \/ e m b e d d i n g s ( \? | $ ) / ,
137+ // Direct endpoints: ends with /embeddings (before query params)
138+ / \/ e m b e d d i n g s ( \? | $ ) / ,
139+ // Some providers use /embed instead of /embeddings
140+ / \/ e m b e d ( \? | $ ) / ,
141+ ]
142+
143+ return patterns . some ( ( pattern ) => pattern . test ( url ) )
125144 }
126145
127146 /**
@@ -155,7 +174,7 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
155174
156175 if ( ! response . ok ) {
157176 const errorText = await response . text ( )
158- const error : any = new Error ( `HTTP ${ response . status } : ${ errorText } ` )
177+ const error = new Error ( `HTTP ${ response . status } : ${ errorText } ` ) as HttpError
159178 error . status = response . status
160179 throw error
161180 }
@@ -173,7 +192,8 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
173192 batchTexts : string [ ] ,
174193 model : string ,
175194 ) : Promise < { embeddings : number [ ] [ ] ; usage : { promptTokens : number ; totalTokens : number } } > {
176- const isFullUrl = this . isFullEndpointUrl ( this . baseUrl )
195+ // Use cached value for performance
196+ const isFullUrl = this . isFullUrl
177197
178198 for ( let attempts = 0 ; attempts < MAX_RETRIES ; attempts ++ ) {
179199 try {
@@ -222,8 +242,9 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
222242 totalTokens : response . usage ?. total_tokens || 0 ,
223243 } ,
224244 }
225- } catch ( error : any ) {
226- const isRateLimitError = error ?. status === 429
245+ } catch ( error ) {
246+ const httpError = error as HttpError
247+ const isRateLimitError = httpError ?. status === 429
227248 const hasMoreAttempts = attempts < MAX_RETRIES - 1
228249
229250 if ( isRateLimitError && hasMoreAttempts ) {
@@ -244,19 +265,19 @@ export class OpenAICompatibleEmbedder implements IEmbedder {
244265
245266 // Provide more context in the error message using robust error extraction
246267 let errorMessage = t ( "embeddings:unknownError" )
247- if ( error ?. message ) {
248- errorMessage = error . message
268+ if ( httpError ?. message ) {
269+ errorMessage = httpError . message
249270 } else if ( typeof error === "string" ) {
250271 errorMessage = error
251- } else if ( error && typeof error . toString === "function" ) {
272+ } else if ( error && typeof error === "object" && "toString" in error ) {
252273 try {
253- errorMessage = error . toString ( )
274+ errorMessage = String ( error )
254275 } catch {
255276 errorMessage = t ( "embeddings:unknownError" )
256277 }
257278 }
258279
259- const statusCode = error ?. status || error ?. response ?. status
280+ const statusCode = httpError ?. status || httpError ?. response ?. status
260281
261282 if ( statusCode === 401 ) {
262283 throw new Error ( t ( "embeddings:authenticationFailed" ) )
0 commit comments