import { useAtomValue } from "@effect/atom-react"; import * as AsyncResult from "effect/unstable/reactivity/AsyncResult"; import { connectionsAtom, sourceAtom } from "@executor-js/react/api/atoms"; import { Badge } from "@executor-js/react/components/badge"; import { Button } from "@executor-js/react/components/button"; import { useScope, useScopeStack, useUserScope } from "@executor-js/react/api/scope-context"; import { ScopeId } from "@executor-js/sdk/core"; import { openApiSourceAtom, openApiSourceBindingsAtom } from "./atoms"; import { effectiveBindingForScope, missingCredentialLabels } from "../sdk/credential-status"; function ConnectedBadge() { return ( Connected ); } function OAuthBadge() { return OAuth; } function NeedsCredentialsBadge() { return ( Needs credentials ); } function CheckingCredentialsBadge() { return ( Checking credentials ); } // The entry row already renders name + id + kind, so this summary // component only contributes extras — specifically, an OAuth status // badge when the source has OAuth2 configured. Non-OAuth sources // render nothing. export default function OpenApiSourceSummary(props: { sourceId: string; variant?: "badge" | "panel"; onAction?: () => void; }) { const displayScope = useScope(); const userScope = useUserScope(); const scopeStack = useScopeStack(); const summaryResult = useAtomValue(sourceAtom(props.sourceId, displayScope)); const sourceScopeId = AsyncResult.isSuccess(summaryResult) && summaryResult.value?.scopeId ? summaryResult.value.scopeId : displayScope; const sourceResult = useAtomValue(openApiSourceAtom(ScopeId.make(sourceScopeId), props.sourceId)); const bindingsResult = useAtomValue( openApiSourceBindingsAtom(displayScope, props.sourceId, ScopeId.make(sourceScopeId)), ); const connectionsResult = useAtomValue(connectionsAtom(displayScope)); const source = AsyncResult.isSuccess(sourceResult) && sourceResult.value ? sourceResult.value : null; if (!source) return null; const oauth2 = source.config.oauth2; const bindingsLoaded = AsyncResult.isSuccess(bindingsResult); const connectionsLoaded = AsyncResult.isSuccess(connectionsResult); if (!bindingsLoaded) { return props.variant === "panel" ? null : ; } const bindings = AsyncResult.isSuccess(bindingsResult) ? bindingsResult.value : []; if (oauth2 && !connectionsLoaded) { return props.variant === "panel" ? null : ; } const connections = AsyncResult.isSuccess(connectionsResult) ? connectionsResult.value : []; const liveConnectionIds = new Set(connections.map((connection) => connection.id as string)); const scopeRanks = new Map( scopeStack.map((scope, index) => [scope.id as string, index] as const), ); const credentialTargetScope = userScope; const missing = missingCredentialLabels(source, bindings, credentialTargetScope, scopeRanks, { liveConnectionIds, }); if (props.variant === "panel") { if (missing.length === 0) return null; return (
This source needs your credentials before tools can run.
Missing: {missing.join(", ")}
{props.onAction && ( )}
); } if (missing.length > 0) return ; if (!oauth2) return null; const connectionBinding = effectiveBindingForScope( bindings, oauth2.connectionSlot, credentialTargetScope, scopeRanks, ); const connectionId = connectionBinding && connectionBinding.value.kind === "connection" ? connectionBinding.value.connectionId : null; if (connectionId && connections.some((connection) => connection.id === connectionId)) { return ; } return ; }