Skip to content

Commit 825c502

Browse files
committed
docs: add JSDoc describing the auto url detection logic
1 parent bc3661c commit 825c502

File tree

1 file changed

+127
-4
lines changed

1 file changed

+127
-4
lines changed

src/api/providers/openai.ts

Lines changed: 127 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,45 @@ import { ResponseCreateParamsNonStreaming } from "openai/resources/responses/res
3030
// TODO: Rename this to OpenAICompatibleHandler. Also, I think the
3131
// `OpenAINativeHandler` can subclass from this, since it's obviously
3232
// compatible with the OpenAI API. We can also rename it to `OpenAIHandler`.
33+
/**
34+
* URL auto-detection overview
35+
*
36+
* Decision tree (host and path based):
37+
* 1) Azure AI Inference Service:
38+
* - Detected when host ends with ".services.ai.azure.com"
39+
* - Uses OpenAI Chat Completions API shape with a path override
40+
* (see OPENAI_AZURE_AI_INFERENCE_PATH) when making requests.
41+
*
42+
* 2) Azure OpenAI:
43+
* - Detected when host is "openai.azure.com" or ends with ".openai.azure.com"
44+
* or when options.openAiUseAzure is explicitly true.
45+
* - Within Azure OpenAI, the API "flavor" is chosen by URL path:
46+
* - Responses API:
47+
* * Path contains "/v1/responses" or ends with "/responses"
48+
* * Also auto-detected for portal-style URLs (e.g. "/openai/responses?api-version=2025-04-01-preview")
49+
* which itself is not valid in request, are normalized to "/openai/v1" with apiVersion "preview".
50+
* - Chat Completions API:
51+
* * Path contains "/chat/completions"
52+
* - Default:
53+
* * Falls back to Chat Completions if none of the above match.
54+
*
55+
* 3) Generic OpenAI-compatible endpoints:
56+
* - Anything else (OpenAI, OpenRouter, LM Studio, vLLM, etc.)
57+
* - Flavor is again selected by URL path as above:
58+
* - "/v1/responses" or ending with "/responses" => Responses API
59+
* - "/chat/completions" => Chat Completions
60+
* - otherwise defaults to Chat Completions for backward compatibility.
61+
*
62+
* Examples:
63+
* - https://api.openai.com/v1 -> Chat Completions (default)
64+
* - https://api.openai.com/v1/responses -> Responses API
65+
* - https://api.openai.com/v1/chat/completions -> Chat Completions
66+
* - https://myres.openai.azure.com/openai/v1/responses?api-version=preview
67+
* -> Azure OpenAI + Responses API
68+
* - https://myres.openai.azure.com/openai/responses?api-version=2025-04-01-preview
69+
* -> normalized to base /openai/v1 + apiVersion "preview" (Responses)
70+
* - https://test.services.ai.azure.com -> Azure AI Inference Service (Chat Completions with path override)
71+
*/
3372
export class OpenAiHandler extends BaseProvider implements SingleCompletionHandler {
3473
protected options: ApiHandlerOptions
3574
private client: OpenAI
@@ -773,16 +812,55 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
773812
}
774813
}
775814

815+
/**
816+
* Detects Grok xAI endpoints.
817+
* - Returns true when the host contains "x.ai" (e.g., "api.x.ai").
818+
* - Used to omit stream_options for streaming requests because Grok may not support them.
819+
*
820+
* Examples:
821+
* - https://api.x.ai/v1 -> true
822+
* - https://api.openai.com/v1 -> false
823+
*/
776824
private _isGrokXAI(baseUrl?: string): boolean {
777825
const urlHost = this._getUrlHost(baseUrl)
778826
return urlHost.includes("x.ai")
779827
}
780828

829+
/**
830+
* Detects Azure AI Inference Service endpoints (distinct from Azure OpenAI).
831+
* - Returns true when host ends with ".services.ai.azure.com".
832+
* - These endpoints require a special path override when calling the Chat Completions API.
833+
*
834+
* Examples:
835+
* - https://myenv.services.ai.azure.com -> true
836+
* - https://myres.openai.azure.com -> false (this is Azure OpenAI, not AI Inference)
837+
*/
781838
private _isAzureAiInference(baseUrl?: string): boolean {
782839
const urlHost = this._getUrlHost(baseUrl)
783840
return urlHost.endsWith(".services.ai.azure.com")
784841
}
785842

843+
/**
844+
* Detects Azure OpenAI "Responses API" URLs by host and path.
845+
* - Host must be "openai.azure.com" or end with ".openai.azure.com"
846+
* - Path may be one of:
847+
* • "/openai/v1/responses" (preferred v1 path)
848+
* • "/openai/responses" (portal/legacy style)
849+
* • any path ending with "/responses"
850+
* - Trailing slashes are trimmed before matching.
851+
*
852+
* This is used to favor the Responses API flavor on Azure OpenAI when the base URL already
853+
* points to a Responses path.
854+
*
855+
* Examples (true):
856+
* - https://myres.openai.azure.com/openai/v1/responses?api-version=preview
857+
* - https://myres.openai.azure.com/openai/responses?api-version=2025-04-01-preview
858+
* - https://openai.azure.com/openai/v1/responses
859+
*
860+
* Examples (false):
861+
* - https://myres.openai.azure.com/openai/v1/chat/completions
862+
* - https://api.openai.com/v1/responses (not an Azure host)
863+
*/
786864
private _isAzureOpenAiResponses(baseUrl?: string): boolean {
787865
try {
788866
if (!baseUrl) return false
@@ -801,10 +879,36 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
801879
}
802880

803881
/**
804-
* Normalize Azure "responses" portal URLs to SDK-friendly base and version.
805-
* - Input (portal sometimes shows): https://{res}.openai.azure.com/openai/responses?api-version=2025-04-01-preview
806-
* - Output: baseURL=https://{res}.openai.azure.com/openai/v1, apiVersionOverride="preview"
807-
* No-op for already-correct or non-Azure URLs.
882+
* Normalizes Azure OpenAI "Responses" portal URLs to an SDK-friendly base and version.
883+
*
884+
* Why:
885+
* - The Azure portal often presents a non-v1 Responses endpoint such as:
886+
* https://{res}.openai.azure.com/openai/responses?api-version=2025-04-01-preview
887+
* which is not the ideal base for SDK clients. We convert it to:
888+
* baseURL = https://{res}.openai.azure.com/openai/v1
889+
* apiVersionOverride = "preview"
890+
*
891+
* What it does:
892+
* - If the input is an Azure OpenAI host and its path is exactly "/openai/responses"
893+
* with api-version=2025-04-01-preview, we:
894+
* • return { baseURL: "https://{host}/openai/v1", apiVersionOverride: "preview" }
895+
* - If the input is already "/openai/v1/responses", we similarly normalize the base to "/openai/v1"
896+
* and set apiVersionOverride to "preview".
897+
* - Otherwise, returns the original URL unchanged.
898+
*
899+
* Scope:
900+
* - Only applies to Azure OpenAI hosts ("openai.azure.com" or "*.openai.azure.com").
901+
* - Non-Azure URLs or already SDK-friendly bases are returned as-is.
902+
*
903+
* Examples:
904+
* - In: https://sample.openai.azure.com/openai/responses?api-version=2025-04-01-preview
905+
* Out: baseURL=https://sample.openai.azure.com/openai/v1, apiVersionOverride="preview"
906+
*
907+
* - In: https://sample.openai.azure.com/openai/v1/responses?api-version=preview
908+
* Out: baseURL=https://sample.openai.azure.com/openai/v1, apiVersionOverride="preview"
909+
*
910+
* - In: https://api.openai.com/v1/responses
911+
* Out: baseURL unchanged (non-Azure)
808912
*/
809913
private _normalizeAzureResponsesBaseUrlAndVersion(inputBaseUrl: string): {
810914
baseURL: string
@@ -866,6 +970,25 @@ export class OpenAiHandler extends BaseProvider implements SingleCompletionHandl
866970

867971
// --- Responses helpers ---
868972

973+
/**
974+
* Determines which OpenAI-compatible API flavor to use based on the URL path.
975+
* - This is purely path-based and provider-agnostic (works for OpenAI, Azure OpenAI after normalization, etc.).
976+
*
977+
* Rules:
978+
* - If path contains "/v1/responses" OR ends with "/responses" => "responses"
979+
* - Else if path contains "/chat/completions" => "chat"
980+
* - Else default to "chat" for backward compatibility
981+
*
982+
* Notes:
983+
* - Trailing slashes are not required to match; we rely on substring checks.
984+
* - Azure "portal" style URLs are normalized beforehand where applicable.
985+
*
986+
* Examples:
987+
* - https://api.openai.com/v1/responses -> "responses"
988+
* - https://api.openai.com/v1/chat/completions -> "chat"
989+
* - https://myres.openai.azure.com/openai/v1 -> "chat" (default)
990+
* - https://myres.openai.azure.com/openai/v1/responses -> "responses"
991+
*/
869992
private _resolveApiFlavor(baseUrl: string): "responses" | "chat" {
870993
// Auto-detect by URL path
871994
const url = this._safeParseUrl(baseUrl)

0 commit comments

Comments
 (0)