azure-openai.ts 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879
  1. export const AZURE_OPENAI_API_VERSION = "2024-10-21"
  2. export function isAzureOpenAiEndpoint(endpoint: string): boolean {
  3. const trimmed = endpoint.trim()
  4. if (!trimmed) return false
  5. try {
  6. const url = new URL(/^https?:\/\//i.test(trimmed) ? trimmed : `https://${trimmed}`)
  7. return url.hostname.toLowerCase().endsWith(".openai.azure.com")
  8. } catch {
  9. return /(^|\/\/)[^/?#]+\.openai\.azure\.com(?::\d+)?(?:[/?#]|$)/i.test(trimmed)
  10. }
  11. }
  12. interface AzureParsedEndpoint {
  13. resourceBase: string
  14. deployment: string
  15. apiVersion: string
  16. }
  17. /**
  18. * Parse an Azure resource URL the user may have pasted in any of the
  19. * common shapes (bare host, host + deployment path, full chat URL).
  20. * The deployment embedded in the path wins over `fallbackDeployment`.
  21. */
  22. function parseAzureOpenAiEndpoint(
  23. endpoint: string,
  24. fallbackDeployment: string,
  25. fallbackApiVersion: string,
  26. ): AzureParsedEndpoint | null {
  27. const trimmed = endpoint.trim()
  28. if (!isAzureOpenAiEndpoint(trimmed)) return null
  29. let apiVersion = (fallbackApiVersion.trim() || AZURE_OPENAI_API_VERSION)
  30. const qMatch = trimmed.match(/[?&]api-version=([^&]+)/i)
  31. if (qMatch) apiVersion = decodeURIComponent(qMatch[1])
  32. const withoutQuery = trimmed.split("?")[0].replace(/\/+$/, "")
  33. const withDeployment = withoutQuery.match(
  34. /^(https?:\/\/[^/]+\.openai\.azure\.com)\/openai\/deployments\/([^/]+)(?:\/chat\/completions)?$/i,
  35. )
  36. if (withDeployment) {
  37. return {
  38. resourceBase: withDeployment[1],
  39. deployment: decodeURIComponent(withDeployment[2]),
  40. apiVersion,
  41. }
  42. }
  43. const resourceOnly = withoutQuery.match(/^(https?:\/\/[^/]+\.openai\.azure\.com)(?:\/openai)?$/i)
  44. if (resourceOnly) {
  45. const deployment = fallbackDeployment.trim()
  46. if (!deployment) return null
  47. return {
  48. resourceBase: resourceOnly[1],
  49. deployment,
  50. apiVersion,
  51. }
  52. }
  53. return null
  54. }
  55. export function buildAzureOpenAiUrl(
  56. endpoint: string,
  57. deployment: string,
  58. apiVersion: string,
  59. ): string {
  60. const parsed = parseAzureOpenAiEndpoint(endpoint, deployment, apiVersion)
  61. if (!parsed) {
  62. const trimmed = endpoint.replace(/\/+$/, "")
  63. const version = encodeURIComponent(apiVersion.trim() || AZURE_OPENAI_API_VERSION)
  64. const deploymentPath = `/openai/deployments/${encodeURIComponent(deployment)}/chat/completions`
  65. return `${trimmed}${deploymentPath}?api-version=${version}`
  66. }
  67. const version = encodeURIComponent(parsed.apiVersion)
  68. const dep = encodeURIComponent(parsed.deployment)
  69. return `${parsed.resourceBase}/openai/deployments/${dep}/chat/completions?api-version=${version}`
  70. }