fix: add Azure validation and make Organization required

- Add Azure case to /api/providers/validate that sends a test chat
  completion with api-key header and organization
- Pass Azure-specific data (endpoint, deployment, apiVersion, org) from
  Add modal to validate endpoint
- Make Organization field required (needed for billing)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kundeng
2026-04-20 01:24:39 -04:00
parent a66a04daab
commit 00bd1a4151
3 changed files with 41 additions and 6 deletions

View File

@@ -25,13 +25,19 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
const [validationResult, setValidationResult] = useState(null); const [validationResult, setValidationResult] = useState(null);
const [saving, setSaving] = useState(false); const [saving, setSaving] = useState(false);
const validatePayload = () => {
const payload = { provider, apiKey: formData.apiKey };
if (isAzure) payload.providerSpecificData = azureData;
return payload;
};
const handleValidate = async () => { const handleValidate = async () => {
setValidating(true); setValidating(true);
try { try {
const res = await fetch("/api/providers/validate", { const res = await fetch("/api/providers/validate", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ provider, apiKey: formData.apiKey }), body: JSON.stringify(validatePayload()),
}); });
const data = await res.json(); const data = await res.json();
setValidationResult(data.valid ? "success" : "failed"); setValidationResult(data.valid ? "success" : "failed");
@@ -54,7 +60,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
const res = await fetch("/api/providers/validate", { const res = await fetch("/api/providers/validate", {
method: "POST", method: "POST",
headers: { "Content-Type": "application/json" }, headers: { "Content-Type": "application/json" },
body: JSON.stringify({ provider, apiKey: formData.apiKey }), body: JSON.stringify(validatePayload()),
}); });
const data = await res.json(); const data = await res.json();
isValid = !!data.valid; isValid = !!data.valid;
@@ -144,7 +150,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
placeholder="2024-10-01-preview" placeholder="2024-10-01-preview"
/> />
<Input <Input
label="Organization (Optional)" label="Organization"
value={azureData.organization} value={azureData.organization}
onChange={(e) => setAzureData({ ...azureData, organization: e.target.value })} onChange={(e) => setAzureData({ ...azureData, organization: e.target.value })}
placeholder="Organization ID" placeholder="Organization ID"
@@ -182,7 +188,7 @@ export default function AddApiKeyModal({ isOpen, provider, providerName, isCompa
</p> </p>
<div className="flex gap-2"> <div className="flex gap-2">
<Button onClick={handleSubmit} fullWidth disabled={!formData.name || !formData.apiKey || (isAzure && (!azureData.azureEndpoint || !azureData.deployment)) || saving}> <Button onClick={handleSubmit} fullWidth disabled={!formData.name || !formData.apiKey || (isAzure && (!azureData.azureEndpoint || !azureData.deployment || !azureData.organization)) || saving}>
{saving ? "Saving..." : "Save"} {saving ? "Saving..." : "Save"}
</Button> </Button>
<Button onClick={onClose} variant="ghost" fullWidth> <Button onClick={onClose} variant="ghost" fullWidth>

View File

@@ -62,6 +62,35 @@ export async function POST(request) {
}); });
} }
if (provider === "azure") {
const { providerSpecificData } = body;
const endpoint = (providerSpecificData?.azureEndpoint || "").replace(/\/$/, "");
const deployment = providerSpecificData?.deployment || "gpt-4";
const apiVersion = providerSpecificData?.apiVersion || "2024-10-01-preview";
const organization = providerSpecificData?.organization;
const url = `${endpoint}/openai/deployments/${deployment}/chat/completions?api-version=${apiVersion}`;
const headers = {
"api-key": apiKey,
"Content-Type": "application/json",
};
if (organization) headers["OpenAI-Organization"] = organization;
const azureRes = await fetch(url, {
method: "POST",
headers,
body: JSON.stringify({
messages: [{ role: "user", content: "test" }],
max_tokens: 1,
}),
});
isValid = azureRes.status !== 401 && azureRes.status !== 403;
return NextResponse.json({
valid: isValid,
error: isValid ? null : "Invalid API key or Azure configuration",
});
}
switch (provider) { switch (provider) {
case "openai": case "openai":
const openaiRes = await fetch("https://api.openai.com/v1/models", { const openaiRes = await fetch("https://api.openai.com/v1/models", {

View File

@@ -215,11 +215,11 @@ export default function EditConnectionModal({ isOpen, connection, proxyPools, on
hint="Azure OpenAI API version to use" hint="Azure OpenAI API version to use"
/> />
<Input <Input
label="Organization (Optional)" label="Organization"
value={azureData.organization} value={azureData.organization}
onChange={(e) => setAzureData({ ...azureData, organization: e.target.value })} onChange={(e) => setAzureData({ ...azureData, organization: e.target.value })}
placeholder="Organization ID" placeholder="Organization ID"
hint="Optional: Your Azure organization ID" hint="Required for billing"
/> />
</div> </div>
</div> </div>