import type { AgentState } from "@letta-ai/letta-client/resources/agents/agents"; import { Box, Text, useInput } from "ink"; import { useEffect, useState } from "react"; import { getClient } from "../../agent/client"; import { colors } from "./colors"; interface AgentSelectorProps { currentAgentId: string; onSelect: (agentId: string) => void; onCancel: () => void; } export function AgentSelector({ currentAgentId, onSelect, onCancel, }: AgentSelectorProps) { const [agents, setAgents] = useState([]); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); const [selectedIndex, setSelectedIndex] = useState(0); const [searchQuery, setSearchQuery] = useState(""); const [debouncedQuery, setDebouncedQuery] = useState(""); useEffect(() => { const fetchAgents = async () => { try { const client = await getClient(); const agentList = await client.agents.list(); setAgents(agentList.items); setLoading(false); } catch (err) { setError(err instanceof Error ? err.message : String(err)); setLoading(false); } }; fetchAgents(); }, []); // Debounce search query (300ms delay) useEffect(() => { const timer = setTimeout(() => { setDebouncedQuery(searchQuery); }, 300); return () => clearTimeout(timer); }, [searchQuery]); // Filter agents based on debounced search query const matchingAgents = agents.filter((agent) => { if (!debouncedQuery) return true; const query = debouncedQuery.toLowerCase(); const name = (agent.name || "").toLowerCase(); const id = (agent.id || "").toLowerCase(); return name.includes(query) || id.includes(query); }); const filteredAgents = matchingAgents.slice(0, 10); // Reset selected index when filtered list changes useEffect(() => { setSelectedIndex(0); }, []); useInput((input, key) => { // CTRL-C: immediately cancel (works even during loading/error) if (key.ctrl && input === "c") { onCancel(); return; } if (loading || error) return; if (key.upArrow) { setSelectedIndex((prev) => Math.max(0, prev - 1)); } else if (key.downArrow) { setSelectedIndex((prev) => Math.min(filteredAgents.length - 1, prev + 1)); } else if (key.return) { const selectedAgent = filteredAgents[selectedIndex]; if (selectedAgent?.id) { onSelect(selectedAgent.id); } } else if (key.escape) { onCancel(); } else if (key.backspace || key.delete) { setSearchQuery((prev) => prev.slice(0, -1)); } else if (input && !key.ctrl && !key.meta) { // Add regular characters to search query setSearchQuery((prev) => prev + input); } }); if (loading) { return ( Loading agents... ); } if (error) { return ( Error loading agents: {error} Press ESC to cancel ); } if (agents.length === 0) { return ( No agents found Press ESC to cancel ); } return ( Select Agent (↑↓ to navigate, Enter to select, ESC to cancel) Search: {searchQuery || "_"} {filteredAgents.length === 0 && ( No agents match your search )} {filteredAgents.length > 0 && ( Showing {filteredAgents.length} {matchingAgents.length > 10 ? ` of ${matchingAgents.length}` : ""} {debouncedQuery ? " matching" : ""} agents )} {filteredAgents.map((agent, index) => { const isSelected = index === selectedIndex; const isCurrent = agent.id === currentAgentId; const lastInteractedAt = agent.last_run_completion ? new Date(agent.last_run_completion).toLocaleString() : "Never"; return ( {isSelected ? "›" : " "} {agent.name || "Unnamed"} {isCurrent && ( (current) )} {agent.id} {lastInteractedAt} ); })} ); }