-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Fix Azure OpenAI API version duplication #5806
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 1 commit
e829647
85215b0
cc7e594
6b691fb
d53df67
252530c
0f12000
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,124 @@ | ||
| import { describe, it, expect } from 'vitest' | ||
| import { extractApiVersionFromUrl, isAzureOpenAiUrl, removeApiVersionFromUrl } from '../azure-url-parser' | ||
|
|
||
| describe('azure-url-parser', () => { | ||
| describe('extractApiVersionFromUrl', () => { | ||
| it('should extract API version from Azure OpenAI URL', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview' | ||
| const result = extractApiVersionFromUrl(url) | ||
| expect(result).toBe('2024-05-01-preview') | ||
| }) | ||
|
|
||
| it('should extract API version from URL with multiple query parameters', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&api-version=2024-12-01-preview&baz=qux' | ||
| const result = extractApiVersionFromUrl(url) | ||
| expect(result).toBe('2024-12-01-preview') | ||
| }) | ||
|
|
||
| it('should return null when no api-version parameter exists', () => { | ||
| const url = 'https://api.openai.com/v1/chat/completions' | ||
| const result = extractApiVersionFromUrl(url) | ||
| expect(result).toBeNull() | ||
| }) | ||
|
|
||
| it('should return null for invalid URLs', () => { | ||
| const invalidUrl = 'not-a-valid-url' | ||
| const result = extractApiVersionFromUrl(invalidUrl) | ||
| expect(result).toBeNull() | ||
| }) | ||
|
|
||
| it('should handle empty api-version parameter', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=' | ||
| const result = extractApiVersionFromUrl(url) | ||
| expect(result).toBe('') | ||
| }) | ||
|
|
||
| it('should handle URL without query parameters', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions' | ||
| const result = extractApiVersionFromUrl(url) | ||
| expect(result).toBeNull() | ||
| }) | ||
| }) | ||
|
|
||
| describe('isAzureOpenAiUrl', () => { | ||
| it('should return true for Azure OpenAI URLs with .openai.azure.com', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(true) | ||
| }) | ||
|
|
||
| it('should return true for Azure URLs ending with .azure.com', () => { | ||
| const url = 'https://myservice.azure.com/api/v1' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(true) | ||
| }) | ||
|
|
||
| it('should return true for URLs with /openai/deployments/ path', () => { | ||
| const url = 'https://custom-domain.com/openai/deployments/mymodel/chat/completions' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(true) | ||
| }) | ||
|
|
||
| it('should return false for regular OpenAI URLs', () => { | ||
| const url = 'https://api.openai.com/v1/chat/completions' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(false) | ||
| }) | ||
|
|
||
| it('should return false for other API URLs', () => { | ||
| const url = 'https://api.anthropic.com/v1/messages' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(false) | ||
| }) | ||
|
|
||
| it('should return false for invalid URLs', () => { | ||
| const invalidUrl = 'not-a-valid-url' | ||
| const result = isAzureOpenAiUrl(invalidUrl) | ||
| expect(result).toBe(false) | ||
| }) | ||
|
|
||
| it('should handle case insensitive hostname matching', () => { | ||
| const url = 'https://MYRESOURCE.OPENAI.AZURE.COM/openai/deployments/mymodel' | ||
| const result = isAzureOpenAiUrl(url) | ||
| expect(result).toBe(true) | ||
| }) | ||
| }) | ||
|
|
||
| describe('removeApiVersionFromUrl', () => { | ||
| it('should remove api-version parameter from URL', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview' | ||
| const result = removeApiVersionFromUrl(url) | ||
| expect(result).toBe('https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions') | ||
| }) | ||
|
|
||
| it('should remove api-version parameter while preserving other parameters', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&api-version=2024-05-01-preview&baz=qux' | ||
| const result = removeApiVersionFromUrl(url) | ||
| expect(result).toBe('https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?foo=bar&baz=qux') | ||
| }) | ||
|
|
||
| it('should return original URL when no api-version parameter exists', () => { | ||
| const url = 'https://api.openai.com/v1/chat/completions?foo=bar' | ||
| const result = removeApiVersionFromUrl(url) | ||
| expect(result).toBe(url) | ||
| }) | ||
|
|
||
| it('should return original URL for invalid URLs', () => { | ||
| const invalidUrl = 'not-a-valid-url' | ||
| const result = removeApiVersionFromUrl(invalidUrl) | ||
| expect(result).toBe(invalidUrl) | ||
| }) | ||
|
|
||
| it('should handle URL with only api-version parameter', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions?api-version=2024-05-01-preview' | ||
| const result = removeApiVersionFromUrl(url) | ||
| expect(result).toBe('https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions') | ||
| }) | ||
|
|
||
| it('should handle URL without query parameters', () => { | ||
| const url = 'https://myresource.openai.azure.com/openai/deployments/mymodel/chat/completions' | ||
| const result = removeApiVersionFromUrl(url) | ||
| expect(result).toBe(url) | ||
| }) | ||
| }) | ||
| }) |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,53 @@ | ||
| /** | ||
| * Utility functions for parsing Azure OpenAI URLs and extracting API versions | ||
| */ | ||
|
|
||
| /** | ||
| * Extracts the API version from an Azure OpenAI URL query parameter | ||
| * @param url The Azure OpenAI URL that may contain an api-version query parameter | ||
| * @returns The extracted API version string, or null if not found | ||
| */ | ||
| export function extractApiVersionFromUrl(url: string): string | null { | ||
| try { | ||
| const urlObj = new URL(url) | ||
| return urlObj.searchParams.get('api-version') | ||
| } catch (error) { | ||
| // Invalid URL format | ||
| return null | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Checks if a URL appears to be an Azure OpenAI URL | ||
| * @param url The URL to check | ||
| * @returns True if the URL appears to be an Azure OpenAI URL | ||
| */ | ||
| export function isAzureOpenAiUrl(url: string): boolean { | ||
| try { | ||
| const urlObj = new URL(url) | ||
| const host = urlObj.host.toLowerCase() | ||
|
|
||
| // Check for Azure OpenAI hostname patterns | ||
| return host.includes('.openai.azure.com') || | ||
| host.endsWith('.azure.com') || | ||
| urlObj.pathname.includes('/openai/deployments/') | ||
| } catch (error) { | ||
| return false | ||
| } | ||
| } | ||
|
|
||
| /** | ||
| * Removes the api-version query parameter from a URL | ||
| * @param url The URL to clean | ||
| * @returns The URL without the api-version parameter | ||
| */ | ||
| export function removeApiVersionFromUrl(url: string): string { | ||
| try { | ||
| const urlObj = new URL(url) | ||
| urlObj.searchParams.delete('api-version') | ||
| return urlObj.toString() | ||
| } catch (error) { | ||
| // Return original URL if parsing fails | ||
| return url | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -12,6 +12,8 @@ import { | |
| openAiModelInfoSaneDefaults, | ||
| } from "@roo-code/types" | ||
|
|
||
| import { extractApiVersionFromUrl, isAzureOpenAiUrl } from "../../../../../src/utils/azure-url-parser" | ||
|
|
||
| import { ExtensionMessage } from "@roo/ExtensionMessage" | ||
|
|
||
| import { useAppTranslation } from "@src/i18n/TranslationContext" | ||
|
|
@@ -41,6 +43,12 @@ export const OpenAICompatible = ({ | |
| const [azureApiVersionSelected, setAzureApiVersionSelected] = useState(!!apiConfiguration?.azureApiVersion) | ||
| const [openAiLegacyFormatSelected, setOpenAiLegacyFormatSelected] = useState(!!apiConfiguration?.openAiLegacyFormat) | ||
|
|
||
| // Check if API version can be extracted from the base URL | ||
| const baseUrl = apiConfiguration?.openAiBaseUrl || "" | ||
| const extractedApiVersion = extractApiVersionFromUrl(baseUrl) | ||
| const isAzureUrl = isAzureOpenAiUrl(baseUrl) | ||
| const showApiVersionExtraction = isAzureUrl && extractedApiVersion && !azureApiVersionSelected | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The variable 'showApiVersionExtraction' is computed based on the extracted API version from the Base URL but is not used anywhere in the UI. Consider using it to display a notice (or auto-fill the Azure API version field) to help users understand that the API version was detected from their Base URL, thereby reducing configuration duplication. |
||
|
|
||
| const [openAiModels, setOpenAiModels] = useState<Record<string, ModelInfo> | null>(null) | ||
|
|
||
| const [customHeaders, setCustomHeaders] = useState<[string, string][]>(() => { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.