From 65f11a603eaf542fc49c39e8d61ec1afcbcb9087 Mon Sep 17 00:00:00 2001 From: decolua Date: Fri, 24 Apr 2026 10:04:59 +0700 Subject: [PATCH] feat: add Azure OpenAI provider support --- open-sse/config/providers.js | 5 ++ open-sse/executors/azure.js | 57 ++++++++++++++ open-sse/executors/index.js | 3 + .../providers/[id]/AddApiKeyModal.js | 51 +++++++++++- src/app/api/providers/[id]/test/testUtils.js | 15 ++++ src/app/api/providers/route.js | 2 +- src/app/api/providers/validate/route.js | 29 +++++++ src/shared/components/EditConnectionModal.js | 78 ++++++++++++++++++- src/shared/constants/providers.js | 2 +- 9 files changed, 236 insertions(+), 6 deletions(-) create mode 100644 open-sse/executors/azure.js diff --git a/open-sse/config/providers.js b/open-sse/config/providers.js index e118bd1a..9e3ba5f7 100644 --- a/open-sse/config/providers.js +++ b/open-sse/config/providers.js @@ -357,6 +357,11 @@ export const PROVIDERS = { format: "perplexity-web", authType: "cookie" }, + azure: { + baseUrl: "", + format: "openai", + headers: {} + }, }; export const OLLAMA_LOCAL_DEFAULT_HOST = "http://localhost:11434"; diff --git a/open-sse/executors/azure.js b/open-sse/executors/azure.js new file mode 100644 index 00000000..574cda22 --- /dev/null +++ b/open-sse/executors/azure.js @@ -0,0 +1,57 @@ +import { DefaultExecutor } from "./default.js"; + +export class AzureExecutor extends DefaultExecutor { + constructor() { + super("azure"); + } + + buildUrl(model, stream, urlIndex = 0, credentials = null) { + const azureEndpoint = credentials?.providerSpecificData?.azureEndpoint + || process.env.AZURE_ENDPOINT + || "https://api.openai.com"; + + const apiVersion = credentials?.providerSpecificData?.apiVersion + || process.env.AZURE_API_VERSION + || "2024-10-01-preview"; + + const deployment = credentials?.providerSpecificData?.deployment + || model + || process.env.AZURE_DEPLOYMENT + || "gpt-4"; + + const endpoint = azureEndpoint.replace(/\/$/, ""); + return `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`; + } + + buildHeaders(credentials, stream = true) { + const headers = { + "Content-Type": "application/json", + ...this.config.headers + }; + + const apiKey = credentials?.apiKey + || credentials?.accessToken + || process.env.OPENAI_API_KEY; + + if (apiKey) { + headers["api-key"] = apiKey; + } + + const organization = credentials?.providerSpecificData?.organization + || process.env.AZURE_ORGANIZATION; + + if (organization) { + headers["OpenAI-Organization"] = organization; + } + + if (stream) { + headers["Accept"] = "text/event-stream"; + } + + return headers; + } + + transformRequest(model, body, stream, credentials) { + return body; + } +} diff --git a/open-sse/executors/index.js b/open-sse/executors/index.js index b7ca8a15..655fb3c0 100644 --- a/open-sse/executors/index.js +++ b/open-sse/executors/index.js @@ -1,4 +1,5 @@ import { AntigravityExecutor } from "./antigravity.js"; +import { AzureExecutor } from "./azure.js"; import { GeminiCLIExecutor } from "./gemini-cli.js"; import { GithubExecutor } from "./github.js"; import { IFlowExecutor } from "./iflow.js"; @@ -16,6 +17,7 @@ import { DefaultExecutor } from "./default.js"; const executors = { antigravity: new AntigravityExecutor(), + azure: new AzureExecutor(), "gemini-cli": new GeminiCLIExecutor(), github: new GithubExecutor(), iflow: new IFlowExecutor(), @@ -47,6 +49,7 @@ export function hasSpecializedExecutor(provider) { export { BaseExecutor } from "./base.js"; export { AntigravityExecutor } from "./antigravity.js"; +export { AzureExecutor } from "./azure.js"; export { GeminiCLIExecutor } from "./gemini-cli.js"; export { GithubExecutor } from "./github.js"; export { IFlowExecutor } from "./iflow.js"; diff --git a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js index 532167d0..91782acd 100644 --- a/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js +++ b/src/app/(dashboard)/dashboard/providers/[id]/AddApiKeyModal.js @@ -13,6 +13,8 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa ? (provider === "grok-web" ? "sso=xxxxx... or just the raw value" : "eyJhbGciOi...") : ""; + const isAzure = provider === "azure"; + const [formData, setFormData] = useState({ name: "", apiKey: "", @@ -20,6 +22,12 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa proxyPoolId: NONE_PROXY_POOL_VALUE, ollamaHostUrl: "", }); + const [azureData, setAzureData] = useState({ + azureEndpoint: "", + apiVersion: "2024-10-01-preview", + deployment: "", + organization: "", + }); const [validating, setValidating] = useState(false); const [validationResult, setValidationResult] = useState(null); const [saving, setSaving] = useState(false); @@ -28,6 +36,14 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa if (isOllamaLocal && formData.ollamaHostUrl.trim()) { return { baseUrl: formData.ollamaHostUrl.trim() }; } + if (isAzure) { + return { + azureEndpoint: azureData.azureEndpoint, + apiVersion: azureData.apiVersion, + deployment: azureData.deployment, + organization: azureData.organization, + }; + } return undefined; }; @@ -164,6 +180,38 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa }

)} + {isAzure && ( +
+

Azure OpenAI Configuration

+
+ setAzureData({ ...azureData, azureEndpoint: e.target.value })} + placeholder="https://your-resource.openai.azure.com" + /> + setAzureData({ ...azureData, deployment: e.target.value })} + placeholder="gpt-4" + /> + setAzureData({ ...azureData, apiVersion: e.target.value })} + placeholder="2024-10-01-preview" + /> + setAzureData({ ...azureData, organization: e.target.value })} + placeholder="Organization ID" + /> +
+
+ )} +
-