From 790daba79633435c229fbd529f9e35ea19818a97 Mon Sep 17 00:00:00 2001 From: miloschwartz Date: Wed, 5 Aug 2026 15:43:42 -0400 Subject: [PATCH] allow no provider on a resource --- messages/en-US.json | 1 - server/routers/resource/createResource.ts | 2 +- .../resource/removeAiProviderFromResource.ts | 20 ++++-------- .../resource/setResourceAiProviders.ts | 4 +-- .../siteResource/createSiteResource.ts | 2 +- .../removeAiProviderFromSiteResource.ts | 20 ++++-------- .../setSiteResourceAiProviders.ts | 4 +-- .../private/[niceId]/providers/page.tsx | 6 ++-- .../public/[niceId]/providers/page.tsx | 6 ++-- .../settings/resources/public/create/page.tsx | 32 +------------------ src/lib/privateResourceForm.ts | 14 -------- 11 files changed, 25 insertions(+), 86 deletions(-) diff --git a/messages/en-US.json b/messages/en-US.json index bf498421a..5528c0ee1 100644 --- a/messages/en-US.json +++ b/messages/en-US.json @@ -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", diff --git a/server/routers/resource/createResource.ts b/server/routers/resource/createResource.ts index 4d546468f..47e70140f 100644 --- a/server/routers/resource/createResource.ts +++ b/server/routers/resource/createResource.ts @@ -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)); diff --git a/server/routers/resource/removeAiProviderFromResource.ts b/server/routers/resource/removeAiProviderFromResource.ts index d3a4294ad..e74b0c886 100644 --- a/server/routers/resource/removeAiProviderFromResource.ts +++ b/server/routers/resource/removeAiProviderFromResource.ts @@ -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); diff --git a/server/routers/resource/setResourceAiProviders.ts b/server/routers/resource/setResourceAiProviders.ts index 0cd0d5d84..88552954b 100644 --- a/server/routers/resource/setResourceAiProviders.ts +++ b/server/routers/resource/setResourceAiProviders.ts @@ -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( diff --git a/server/routers/siteResource/createSiteResource.ts b/server/routers/siteResource/createSiteResource.ts index 277c853ab..feeca0938 100644 --- a/server/routers/siteResource/createSiteResource.ts +++ b/server/routers/siteResource/createSiteResource.ts @@ -353,7 +353,7 @@ export async function createSiteResource( const resolved = await resolveProviderAttachments({ orgId, attachments: aiProviderInputs ?? [], - requireAtLeastOne: true + requireAtLeastOne: false }); if (isInferenceFieldsError(resolved)) { return next( diff --git a/server/routers/siteResource/removeAiProviderFromSiteResource.ts b/server/routers/siteResource/removeAiProviderFromSiteResource.ts index 65781859c..3d4392820 100644 --- a/server/routers/siteResource/removeAiProviderFromSiteResource.ts +++ b/server/routers/siteResource/removeAiProviderFromSiteResource.ts @@ -63,8 +63,9 @@ export async function removeAiProviderFromSiteResource( next: NextFunction ): Promise { 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); diff --git a/server/routers/siteResource/setSiteResourceAiProviders.ts b/server/routers/siteResource/setSiteResourceAiProviders.ts index c6e708064..fd685bb20 100644 --- a/server/routers/siteResource/setSiteResourceAiProviders.ts +++ b/server/routers/siteResource/setSiteResourceAiProviders.ts @@ -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( diff --git a/src/app/[orgId]/settings/resources/private/[niceId]/providers/page.tsx b/src/app/[orgId]/settings/resources/private/[niceId]/providers/page.tsx index ead7ab24b..dcbc4ea29 100644 --- a/src/app/[orgId]/settings/resources/private/[niceId]/providers/page.tsx +++ b/src/app/[orgId]/settings/resources/private/[niceId]/providers/page.tsx @@ -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; diff --git a/src/app/[orgId]/settings/resources/public/[niceId]/providers/page.tsx b/src/app/[orgId]/settings/resources/public/[niceId]/providers/page.tsx index 67ef0fa9e..51182c2a8 100644 --- a/src/app/[orgId]/settings/resources/public/[niceId]/providers/page.tsx +++ b/src/app/[orgId]/settings/resources/public/[niceId]/providers/page.tsx @@ -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; diff --git a/src/app/[orgId]/settings/resources/public/create/page.tsx b/src/app/[orgId]/settings/resources/public/create/page.tsx index c0e0a0f8e..a5088d84e 100644 --- a/src/app/[orgId]/settings/resources/public/create/page.tsx +++ b/src/app/[orgId]/settings/resources/public/create/page.tsx @@ -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 - ); - } }} />

@@ -1455,13 +1443,6 @@ export default function Page() { "aiResourceProvidersHelp" )}

- {showProvidersError && ( -

- {t( - "aiResourceProvidersRequired" - )} -

- )} @@ -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(); } diff --git a/src/lib/privateResourceForm.ts b/src/lib/privateResourceForm.ts index 4c60f1cef..e336a45a1 100644 --- a/src/lib/privateResourceForm.ts +++ b/src/lib/privateResourceForm.ts @@ -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"] - }); - } }); }