Handle warning and no routing to remote exit nodes for ai providers

This commit is contained in:
Owen
2026-08-21 12:09:22 -04:00
parent 2e87927b83
commit e3e1508e8a
6 changed files with 40 additions and 1 deletions
+1
View File
@@ -1891,6 +1891,7 @@
"aiProviderRoutingModeTargetDescription": "Route through targets on your sites",
"aiProviderRoutingModeTargetNote": "After creating this provider, configure site targets on the Network Settings tab.",
"aiProviderTargetNoOne": "This provider doesn't have any targets. Add a target to route requests through your sites.",
"aiProviderRemoteNodeTargetsWarning": "Sites connected to remote nodes are inaccessable to be routed to on AI Gateway providers.",
"aiProviderSkipTlsVerification": "Skip TLS Verification",
"aiProviderSkipTlsVerificationDescription": "Disable TLS certificate verification for the upstream connection",
"aiProviderBudget": "Budget",
@@ -99,6 +99,7 @@ async function fetchProviderTargets(
method: targets.method,
exitNodeSubnet: sites.exitNodeSubnet,
reachableAt: exitNodes.reachableAt,
exitNodeType: exitNodes.type,
hcHealth: targetHealthCheck.hcHealth
})
.from(targets)
@@ -119,6 +120,12 @@ async function fetchProviderTargets(
if (!row.exitNodeSubnet || !row.reachableAt) {
continue;
}
// Sites connected to a remote exit node aren't reachable via a
// gerbil sidecar's /router/* proxy - only "gerbil" type exit nodes
// run that endpoint.
if (row.exitNodeType !== "gerbil") {
continue;
}
// A target with an active health check that's currently failing is
// taken out of rotation. No health check (null) or "unknown" (check
// hasn't run yet / hcEnabled is off) still routes normally, matching
@@ -345,6 +345,7 @@ export default function AiProviderNetworkPage() {
ref={targetsFormRef}
orgId={orgId}
isHttp
isAiProvider
providerId={provider.providerId}
initialTargets={
isTargetModeSaved ? remoteTargets : []
@@ -682,6 +682,7 @@ export default function CreateAiProviderPage() {
<ProxyResourceTargetsForm
orgId={orgId}
isHttp
isAiProvider
onChange={(nextTargets) => {
targetsRef.current = nextTargets;
}}
@@ -113,6 +113,8 @@ type ProxyResourceTargetsFormProps = {
hideSaveButton?: boolean;
/** Hide the advanced mode toggle and always use non-advanced mode (e.g. AI providers) */
disableAdvancedMode?: boolean;
/** Targets picker is for an AI provider (changes which routing warnings are shown) */
isAiProvider?: boolean;
};
export const ProxyResourceTargetsForm = forwardRef<
@@ -131,7 +133,8 @@ export const ProxyResourceTargetsForm = forwardRef<
emptyMessage,
embedded = false,
hideSaveButton = false,
disableAdvancedMode = false
disableAdvancedMode = false,
isAiProvider = false
},
ref
) {
@@ -259,6 +262,14 @@ export const ProxyResourceTargetsForm = forwardRef<
})
);
const { data: remoteExitNodes = [] } = useQuery({
...orgQueries.remoteExitNodes({ orgId }),
enabled: build === "saas" && isAiProvider
});
const hasRemoteExitNodes = remoteExitNodes.some(
(node) => node.exitNodeId !== null
);
const updateTarget = useCallback(
(targetId: number, data: Partial<LocalTarget>) => {
setTargets((prevTargets) => {
@@ -972,6 +983,7 @@ export const ProxyResourceTargetsForm = forwardRef<
</div>
)}
{build === "saas" &&
!isAiProvider &&
targets.length > 1 &&
new Set(targets.map((t) => t.siteId)).size > 1 && (
<p className="text-sm text-muted-foreground mt-3">
@@ -988,6 +1000,11 @@ export const ProxyResourceTargetsForm = forwardRef<
.
</p>
)}
{build === "saas" && isAiProvider && hasRemoteExitNodes && (
<p className="text-sm text-muted-foreground mt-3">
{t("aiProviderRemoteNodeTargetsWarning")}
</p>
)}
</>
);
+12
View File
@@ -59,6 +59,7 @@ import type {
import type { GetResourceResponse } from "@server/routers/resource/getResource";
import type { GetResourceAuthInfoResponse } from "@server/routers/resource/getResourceAuthInfo";
import type { ListResourcePoliciesResponse } from "@server/routers/resource/types";
import type { ListRemoteExitNodesResponse } from "@server/routers/remoteExitNode/types";
import type { ListRolesResponse } from "@server/routers/role";
import type { ListSitesResponse } from "@server/routers/site";
import type {
@@ -330,6 +331,17 @@ export const orgQueries = {
}
}),
remoteExitNodes: ({ orgId }: { orgId: string }) =>
queryOptions({
queryKey: ["ORG", orgId, "REMOTE_EXIT_NODES"] as const,
queryFn: async ({ signal, meta }) => {
const res = await meta!.api.get<
AxiosResponse<ListRemoteExitNodesResponse>
>(`/org/${orgId}/remote-exit-nodes`, { signal });
return res.data.data.remoteExitNodes;
}
}),
labels: ({
orgId,
query,