import { useEffect, useMemo, useRef, useState, type ReactNode } from "react"; import type { Loadable } from "../../core/types"; import { usePrefetchToolDetail, } from "../../hooks/sources"; import type { SourceInspection, SourceInspectionToolDetail, } from "@executor/platform-sdk/schema"; import { MethodBadge } from "./ui/badge"; import { CodeBlock } from "./code-block"; import { DocumentPanel } from "./document-panel"; import { EmptyState, LoadableBlock } from "./loadable"; import { Markdown } from "./markdown"; import { IconCheck, IconChevron, IconClose, IconCopy, IconFolder, IconSearch, IconTool, } from "./icons"; import { ToolPermissionBadge, ToolPermissionDot, resolveToolPermission, type ToolPermissionLevel } from "./tool-permission-badge"; import type { LocalScopePolicy } from "@executor/platform-sdk/schema"; import { Button } from "./ui/button"; import { Input } from "./ui/input"; import { cn } from "../lib/cn"; type ToolTreeNode = { segment: string; tool?: SourceInspectionToolDetail["summary"] | SourceInspection["tools"][number]; children: Map; }; export type SourceToolDiscoveryResult = { query: string; results: ReadonlyArray<{ path: string; score: number; description?: string | null; }>; }; export type ToolPermissionChangeRequest = { toolPath: string; level: ToolPermissionLevel; }; export type SourceToolDetailPanelProps = { detail: SourceInspectionToolDetail; policies?: ReadonlyArray; onSetPermission?: (request: ToolPermissionChangeRequest) => void | Promise; permissionPending?: boolean; renderHeaderMeta?: (detail: SourceInspectionToolDetail) => ReactNode; renderSchemaExtras?: (detail: SourceInspectionToolDetail) => ReactNode; }; export type SourceToolModelWorkbenchProps = { bundle: SourceInspection; detail: Loadable; selectedToolPath: string | null; onSelectTool: (toolPath: string) => void; sourceId: string; policies?: ReadonlyArray; onSetPermission?: (request: ToolPermissionChangeRequest) => void | Promise; permissionPending?: boolean; renderDetail?: (detail: SourceInspectionToolDetail) => ReactNode; }; export type SourceToolDiscoveryPanelProps< TResult extends SourceToolDiscoveryResult = SourceToolDiscoveryResult, > = { discovery: Loadable; initialQuery: string; onSubmitQuery: (query: string) => void; onOpenTool: (toolPath: string) => void; }; const buildToolTree = (tools: SourceInspection["tools"]): ToolTreeNode => { const root: ToolTreeNode = { segment: "", children: new Map(), }; for (const tool of tools) { const parts = tool.path.split("."); let node = root; for (const part of parts) { const existing = node.children.get(part); if (existing) { node = existing; continue; } const next: ToolTreeNode = { segment: part, children: new Map(), }; node.children.set(part, next); node = next; } node.tool = tool; } return root; }; const countToolLeaves = (node: ToolTreeNode): number => { let count = node.tool ? 1 : 0; for (const child of node.children.values()) { count += countToolLeaves(child); } return count; }; const highlightMatch = (text: string, search: string) => { if (!search.trim()) { return text; } const terms = search.trim().toLowerCase().split(/\s+/); const lowerText = text.toLowerCase(); const ranges: Array<[number, number]> = []; for (const term of terms) { let index = 0; while (index < lowerText.length) { const found = lowerText.indexOf(term, index); if (found === -1) { break; } ranges.push([found, found + term.length]); index = found + 1; } } if (ranges.length === 0) { return text; } ranges.sort((left, right) => left[0] - right[0]); const merged: Array<[number, number]> = [ranges[0]!]; for (let index = 1; index < ranges.length; index++) { const last = merged[merged.length - 1]!; const current = ranges[index]!; if (current[0] <= last[1]) { last[1] = Math.max(last[1], current[1]); } else { merged.push(current); } } const parts: Array<{ text: string; highlighted: boolean }> = []; let cursor = 0; for (const [start, end] of merged) { if (cursor < start) { parts.push({ text: text.slice(cursor, start), highlighted: false }); } parts.push({ text: text.slice(start, end), highlighted: true }); cursor = end; } if (cursor < text.length) { parts.push({ text: text.slice(cursor), highlighted: false }); } return ( <> {parts.map((part, index) => part.highlighted ? ( {part.text} ) : ( {part.text} ), )} ); }; const CopyButton = (props: { text: string; field: string; copiedField: string | null; onCopy: (text: string, field: string) => void | Promise; }) => ( ); const SourceToolTree = (props: { tools: SourceInspection["tools"]; selectedToolPath: string | null; onSelectTool: (path: string) => void; search: string; isFiltered: boolean; sourceId: string; policies?: ReadonlyArray; }) => { const tree = useMemo(() => buildToolTree(props.tools), [props.tools]); const prefetch = usePrefetchToolDetail(); const entries = [...tree.children.values()].sort((left, right) => left.segment.localeCompare(right.segment) ); return (
{entries.map((node) => ( ))}
); }; const SourceToolTreeNodeView = (props: { node: ToolTreeNode; depth: number; selectedToolPath: string | null; onSelectTool: (path: string) => void; search: string; defaultOpen: boolean; sourceId: string; prefetch: ReturnType; policies?: ReadonlyArray; }) => { const { node, depth, selectedToolPath, onSelectTool, search, defaultOpen, sourceId, prefetch, } = props; const hasChildren = node.children.size > 0; const isLeaf = !!node.tool && !hasChildren; const hasSelectedDescendant = useMemo(() => { if (!selectedToolPath) { return false; } const check = (candidate: ToolTreeNode): boolean => { if (candidate.tool?.path === selectedToolPath) { return true; } for (const child of candidate.children.values()) { if (check(child)) { return true; } } return false; }; return check(node); }, [node, selectedToolPath]); const [open, setOpen] = useState(defaultOpen || hasSelectedDescendant); useEffect(() => { if (defaultOpen || hasSelectedDescendant) { setOpen(true); } }, [defaultOpen, hasSelectedDescendant]); if (isLeaf) { return ( onSelectTool(node.tool!.path)} search={search} depth={depth} sourceId={sourceId} prefetch={prefetch} policies={props.policies} /> ); } const paddingLeft = 8 + depth * 16; const sortedChildren = [...node.children.values()].sort((left, right) => left.segment.localeCompare(right.segment) ); const leafCount = countToolLeaves(node); return (
{node.tool ? (
onSelectTool(node.tool!.path)} search={search} depth={-1} className="flex-1 pl-1" sourceId={sourceId} prefetch={prefetch} policies={props.policies} />
) : ( )} {open && hasChildren && (
{sortedChildren.map((child) => ( ))}
)}
); }; const SourceToolListItem = (props: { tool: SourceInspection["tools"][number]; active: boolean; onSelect: () => void; search: string; depth: number; className?: string; sourceId: string; prefetch: ReturnType; policies?: ReadonlyArray; }) => { const ref = useRef(null); const paddingLeft = props.depth >= 0 ? 8 + props.depth * 16 + 8 : undefined; useEffect(() => { if (props.active && ref.current) { ref.current.scrollIntoView({ block: "nearest" }); } }, [props.active]); const label = props.depth >= 0 ? props.tool.path.split(".").pop() ?? props.tool.path : props.tool.path; return ( ); }; const permissionLevels: Array<{ level: ToolPermissionLevel; label: string; description: string; dotClass: string; activeClass: string; }> = [ { level: "auto-run", label: "Auto-run", description: "Execute without asking", dotClass: "bg-emerald-500", activeClass: "border-emerald-500/30 bg-emerald-500/10 text-emerald-700 dark:text-emerald-300", }, { level: "requires-approval", label: "Approval", description: "Ask before running", dotClass: "bg-amber-500", activeClass: "border-amber-500/30 bg-amber-500/10 text-amber-700 dark:text-amber-300", }, { level: "denied", label: "Denied", description: "Block completely", dotClass: "bg-red-500", activeClass: "border-red-500/30 bg-red-500/10 text-red-700 dark:text-red-300", }, ]; const ToolPermissionControl = (props: { toolPath: string; policies: ReadonlyArray; onSetPermission: (request: ToolPermissionChangeRequest) => void | Promise; pending?: boolean; }) => { const { level, matchedPolicy } = resolveToolPermission( props.toolPath, props.policies, ); return (
Permission {matchedPolicy && ( {matchedPolicy.resourcePattern} )}
{permissionLevels.map((option) => { const isActive = level === option.level; return ( ); })}
); }; export const SourceToolDetailPanel = (props: SourceToolDetailPanelProps) => { const [copiedField, setCopiedField] = useState(null); const inputType = props.detail.contract.input.typeDeclaration ?? props.detail.contract.input.typePreview ?? null; const outputType = props.detail.contract.output.typeDeclaration ?? props.detail.contract.output.typePreview ?? null; return (

{props.detail.summary.path}

{ await navigator.clipboard.writeText(text); setCopiedField(field); window.setTimeout(() => setCopiedField(null), 1500); }} />
{props.detail.summary.method && ( )} {props.policies && ( )} {props.renderHeaderMeta?.(props.detail)}
{props.onSetPermission && props.policies && ( )} {props.detail.summary.description && ( {props.detail.summary.description} )}
{props.renderSchemaExtras?.(props.detail)}
{props.detail.sections.map((section, index) => (
{section.title}
{section.kind === "facts" ? (
{section.items.map((item) => (
{item.label}:{" "} {item.value}
))}
) : section.kind === "markdown" ? (
{section.body}
) : ( section.body ? ( ) : (
No content.
) )}
))}
); }; export const SourceToolModelWorkbench = ( props: SourceToolModelWorkbenchProps, ) => { const [search, setSearch] = useState(""); const searchRef = useRef(null); const terms = search.trim().toLowerCase().split(/\s+/).filter(Boolean); const filteredTools = useMemo( () => props.bundle.tools.filter((tool) => { if (terms.length === 0) { return true; } const corpus = [ tool.path, tool.method ?? "", tool.inputTypePreview ?? "", tool.outputTypePreview ?? "", ] .join(" ") .toLowerCase(); return terms.every((term) => corpus.includes(term)); }), [props.bundle.tools, terms], ); useEffect(() => { const handler = (event: KeyboardEvent) => { if (event.key === "/" && document.activeElement?.tagName !== "INPUT") { event.preventDefault(); searchRef.current?.focus(); } if (event.key === "Escape") { searchRef.current?.blur(); if (search.length > 0) { setSearch(""); } } }; document.addEventListener("keydown", handler); return () => document.removeEventListener("keydown", handler); }, [search]); const policies = props.policies; const onSetPermission = props.onSetPermission; const permissionPending = props.permissionPending; const renderDetail = props.renderDetail ?? ((detail: SourceInspectionToolDetail) => ( )); return ( <>
setSearch(event.target.value)} placeholder={`Filter ${props.bundle.toolCount} tools...`} className="min-w-0 flex-1 border-0 bg-transparent p-0 text-[13px] shadow-none ring-0 focus:border-0 focus:ring-0" /> {search.length > 0 ? ( ) : ( / )}
{filteredTools.length === 0 ? (
{terms.length > 0 ? "No tools match your filter" : "No tools available"}
) : (
0} sourceId={props.sourceId} policies={props.policies} />
)}
{(detail) => detail ? ( renderDetail(detail) ) : ( 0 ? "Select a tool" : "No tools available"} description={ props.bundle.toolCount > 0 ? "Choose from the list or press / to search." : undefined } /> ) }
); }; export const SourceToolDiscoveryPanel = < TResult extends SourceToolDiscoveryResult = SourceToolDiscoveryResult, >( props: SourceToolDiscoveryPanelProps, ) => { const [draftQuery, setDraftQuery] = useState(props.initialQuery); useEffect(() => { setDraftQuery(props.initialQuery); }, [props.initialQuery]); return (
{ event.preventDefault(); props.onSubmitQuery(draftQuery.trim()); }} >
setDraftQuery(event.target.value)} placeholder="Search tools..." className="pl-9 pr-3 text-sm" />
{(result) => result.query.length === 0 ? ( ) : result.results.length === 0 ? ( ) : (
{result.results.map((item, index) => ( ))}
) }
); };