allow no provider on a resource

This commit is contained in:
miloschwartz
2026-08-05 15:43:42 -04:00
parent bcf6b86b84
commit 790daba796
11 changed files with 25 additions and 86 deletions
-1
View File
@@ -1739,7 +1739,6 @@
"aiResourceProvidersHelp": "Models must be defined on each provider. Model names cannot overlap across selected providers.",
"aiResourceProvidersSelect": "Select providers",
"aiResourceProvidersEmpty": "No AI providers found",
"aiResourceProvidersRequired": "Select at least one AI provider",
"aiResourceProvidersUpdated": "Providers updated",
"aiResourceProvidersErrorUpdate": "Failed to update providers",
"aiResourceAliasRequired": "Alias is required for inference resources",
+1 -1
View File
@@ -394,7 +394,7 @@ async function createHttpResource(
const resolved = await resolveProviderAttachments({
orgId,
attachments: aiProviderInputs ?? [],
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(resolved)) {
return next(createHttpError(HttpCode.BAD_REQUEST, resolved.error));
@@ -77,8 +77,9 @@ export async function removeAiProviderFromResource(
const { providerId } = parsedBody.data;
const parsedParams =
removeAiProviderFromResourceParamsSchema.safeParse(req.params);
const parsedParams = removeAiProviderFromResourceParamsSchema.safeParse(
req.params
);
if (!parsedParams.success) {
return next(
createHttpError(
@@ -129,22 +130,15 @@ export async function removeAiProviderFromResource(
modelAccessMode: a.modelAccessMode
}));
if (remaining.length === 0) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"At least one AI provider is required for inference-mode resources"
)
);
}
const attachments = await resolveProviderAttachments({
orgId: resource.orgId,
attachments: remaining,
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(createHttpError(HttpCode.BAD_REQUEST, attachments.error));
return next(
createHttpError(HttpCode.BAD_REQUEST, attachments.error)
);
}
await setPublicResourceAiProviders(resourceId, attachments);
@@ -27,7 +27,7 @@ registry.registerPath({
method: "post",
path: "/resource/{resourceId}/ai-providers",
description:
"Replace the AI providers attached to an inference resource. At least one provider is required. Model keys must be unique across attached catalog providers.",
"Replace the AI providers attached to an inference resource. An empty list clears all providers. Model keys must be unique across attached catalog providers.",
tags: [OpenAPITags.PublicResource],
request: {
params: setResourceAiProvidersParamsSchema,
@@ -113,7 +113,7 @@ export async function setResourceAiProviders(
const attachments = await resolveProviderAttachments({
orgId: resource.orgId,
attachments: providers,
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -353,7 +353,7 @@ export async function createSiteResource(
const resolved = await resolveProviderAttachments({
orgId,
attachments: aiProviderInputs ?? [],
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(resolved)) {
return next(
@@ -63,8 +63,9 @@ export async function removeAiProviderFromSiteResource(
next: NextFunction
): Promise<any> {
try {
const parsedBody =
removeAiProviderFromSiteResourceBodySchema.safeParse(req.body);
const parsedBody = removeAiProviderFromSiteResourceBodySchema.safeParse(
req.body
);
if (!parsedBody.success) {
return next(
createHttpError(
@@ -128,22 +129,15 @@ export async function removeAiProviderFromSiteResource(
modelAccessMode: a.modelAccessMode
}));
if (remaining.length === 0) {
return next(
createHttpError(
HttpCode.BAD_REQUEST,
"At least one AI provider is required for inference-mode resources"
)
);
}
const attachments = await resolveProviderAttachments({
orgId: siteResource.orgId,
attachments: remaining,
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(createHttpError(HttpCode.BAD_REQUEST, attachments.error));
return next(
createHttpError(HttpCode.BAD_REQUEST, attachments.error)
);
}
await setSiteResourceAiProviders(siteResourceId, attachments);
@@ -27,7 +27,7 @@ registry.registerPath({
method: "post",
path: "/site-resource/{siteResourceId}/ai-providers",
description:
"Replace the AI providers attached to an inference site resource. At least one provider is required. Model keys must be unique across attached catalog providers.",
"Replace the AI providers attached to an inference site resource. An empty list clears all providers. Model keys must be unique across attached catalog providers.",
tags: [OpenAPITags.PrivateResource],
request: {
params: setSiteResourceAiProvidersParamsSchema,
@@ -115,7 +115,7 @@ export async function setSiteResourceAiProviders(
const attachments = await resolveProviderAttachments({
orgId: siteResource.orgId,
attachments: providers,
requireAtLeastOne: true
requireAtLeastOne: false
});
if (isInferenceFieldsError(attachments)) {
return next(
@@ -58,11 +58,9 @@ export default function PrivateResourceProvidersPage() {
const formSchema = useMemo(
() =>
z.object({
providerIds: z
.array(z.number().int().positive())
.min(1, t("aiResourceProvidersRequired"))
providerIds: z.array(z.number().int().positive())
}),
[t]
[]
);
type FormValues = z.infer<typeof formSchema>;
@@ -58,11 +58,9 @@ export default function PublicResourceProvidersPage() {
const formSchema = useMemo(
() =>
z.object({
providerIds: z
.array(z.number().int().positive())
.min(1, t("aiResourceProvidersRequired"))
providerIds: z.array(z.number().int().positive())
}),
[t]
[]
);
type FormValues = z.infer<typeof formSchema>;
@@ -252,7 +252,6 @@ export default function Page() {
const [selectedProviders, setSelectedProviders] = useState<
SelectedAiProvider[]
>([]);
const [showProvidersError, setShowProvidersError] = useState(false);
// SSH-specific state
const [sshServerMode, setSshServerMode] = useState<"standard" | "native">(
@@ -355,9 +354,6 @@ export default function Page() {
if (!availableTypes.includes(resourceType)) {
setResourceType("http");
}
if (resourceType !== "inference") {
setShowProvidersError(false);
}
}, [availableTypes, resourceType]);
const baseResourceFormSchema = useMemo(
@@ -1440,14 +1436,6 @@ export default function Page() {
setSelectedProviders(
providers
);
if (
providers.length >
0
) {
setShowProvidersError(
false
);
}
}}
/>
<p className="text-sm text-muted-foreground">
@@ -1455,13 +1443,6 @@ export default function Page() {
"aiResourceProvidersHelp"
)}
</p>
{showProvidersError && (
<p className="text-sm text-destructive">
{t(
"aiResourceProvidersRequired"
)}
</p>
)}
</div>
</SettingsFormCell>
</SettingsFormGrid>
@@ -1493,16 +1474,6 @@ export default function Page() {
const tcpValid = !isHttpResource
? await tcpUdpForm.trigger()
: true;
const providersValid =
resourceType !== "inference" ||
selectedProviders.length > 0;
if (
resourceType === "inference" &&
!providersValid
) {
setShowProvidersError(true);
}
if (
resourceType === "ssh" &&
@@ -1527,8 +1498,7 @@ export default function Page() {
baseValid &&
domainValid &&
tcpValid &&
bgValid &&
providersValid
bgValid
) {
onSubmit();
}
-14
View File
@@ -467,13 +467,6 @@ export function createCreateFormSchema(t: TranslateFn) {
path: ["alias"]
});
}
if (!data.providerIds || data.providerIds.length < 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceProvidersRequired"),
path: ["providerIds"]
});
}
}
if (
data.mode !== "ssh" &&
@@ -639,13 +632,6 @@ export function createInferenceFormSchema(t: TranslateFn) {
path: ["alias"]
});
}
if (!data.providerIds || data.providerIds.length < 1) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: t("aiResourceProvidersRequired"),
path: ["providerIds"]
});
}
});
}