UNPKG

local-agent

Version:

A CLI agentic system for orchestrating tools and memory with per-folder scoping

143 lines 6.49 kB
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime"; import { useState, useCallback, useEffect } from "react"; import { Box, Text } from "ink"; import TextInput from "ink-text-input"; import Spinner from "ink-spinner"; import { validateAndLoadFiles, loadAllMcpTools } from "./initialization.js"; import { marked } from "marked"; import TerminalRenderer from "marked-terminal"; // MarkdownRenderer using marked + marked-terminal (CJS compatible) const MarkdownRenderer = (props) => { const ansi = marked(props.children, { renderer: new TerminalRenderer() }); return _jsx(Text, { children: ansi }); }; import { createAISDKTools } from "@agentic/ai-sdk"; import { openai } from "@ai-sdk/openai"; import { anthropic } from "@ai-sdk/anthropic"; import { google } from "@ai-sdk/google"; import { generateText } from "ai"; const REQUIRED_FILES = ["local-agent.json", "mcp-tools.json", "keys.json", "system.md"]; const MEMORY_DIR = "memory"; // Model selection logic from interactions.ts function parseModelString(modelString) { if (!modelString.includes("/")) { return { provider: "openai", modelName: modelString }; } const [provider, ...rest] = modelString.split("/"); return { provider: provider.toLowerCase(), modelName: rest.join("/") }; } function getClientForProvider(provider) { switch (provider) { case "openai": return openai; case "anthropic": return anthropic; case "google": return google; default: throw new Error(`Unsupported AI provider: ${provider}`); } } const App = () => { const [input, setInput] = useState(""); const [conversation, setConversation] = useState([]); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const [config, setConfig] = useState(null); const [tools, setTools] = useState(null); const [keys, setKeys] = useState(null); const [loadedTools, setLoadedTools] = useState(null); const agentName = config?.name || "Local Agent"; // Load config, tools, and keys on mount useEffect(() => { (async () => { try { const { config, tools, keys } = await validateAndLoadFiles(REQUIRED_FILES, MEMORY_DIR); setConfig(config); setTools(tools); setKeys(keys); const { loadedTools } = await loadAllMcpTools(tools); setLoadedTools(loadedTools); setIsLoading(false); } catch (e) { setError(e.message || "Failed to load configuration or tools."); setIsLoading(false); } })(); }, []); // Build conversation context string (last 10 messages) const buildConversationContext = () => { if (conversation.length === 0) return ""; const recent = conversation.slice(-10); let context = "\n\nCONVERSATION HISTORY:\n"; recent.forEach((msg) => { const timeStr = msg.timestamp.toLocaleTimeString(); context += `[${timeStr}] ${msg.role.toUpperCase()}: ${msg.content}\n`; if (msg.toolUsed) { context += `[${timeStr}] TOOL_USED: ${msg.toolUsed}\n`; } }); return context; }; const handleSubmit = useCallback(async (value) => { setError(null); setIsLoading(true); const userMsg = { role: "user", content: value, timestamp: new Date() }; setConversation((prev) => [...prev, userMsg]); setInput(""); try { // Prepare tools for LLM const mcpToolInstances = loadedTools ? Object.values(loadedTools) : []; const allTools = mcpToolInstances.length === 1 ? createAISDKTools(mcpToolInstances[0]) : createAISDKTools(...mcpToolInstances); // Build prompt with context const contextualPrompt = value + buildConversationContext(); // Model selection const { provider, modelName } = parseModelString(config.model); const client = getClientForProvider(provider); const model = client(modelName); const result = await generateText({ model, tools: allTools, temperature: config.temperature, system: config.system, prompt: contextualPrompt }); let assistantResponse = ""; if (typeof result.text === "string" && result.text.trim() !== "") { assistantResponse = result.text; } else { assistantResponse = JSON.stringify(result, null, 2); } const agentMsg = { role: "assistant", content: assistantResponse, timestamp: new Date() }; setConversation((prev) => [...prev, agentMsg]); } catch (e) { setError(e.message || "Unknown error"); } finally { setIsLoading(false); } }, [loadedTools, config, conversation]); if (isLoading) { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: "yellow", children: [_jsx(Spinner, { type: "dots" }), " Loading configuration and tools..."] }) })); } if (error) { return (_jsx(Box, { flexDirection: "column", padding: 1, children: _jsxs(Text, { color: "red", children: ["Error: ", error] }) })); } return (_jsxs(Box, { flexDirection: "column", padding: 1, children: [_jsx(Text, { color: "green", children: "Welcome to the Ink-based Local Agent CLI!" }), _jsxs(Text, { children: ["Type your prompt for ", _jsx(Text, { color: "yellow", children: agentName }), " (Ctrl+C to exit):"] }), _jsx(Box, { flexDirection: "column", marginTop: 1, marginBottom: 1, children: conversation.map((msg, idx) => (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: msg.role === "user" ? "blue" : msg.role === "assistant" ? "yellow" : "gray", children: [msg.role === "user" ? "You" : agentName, ":"] }), _jsx(MarkdownRenderer, { children: msg.content })] }, idx))) }), _jsx(TextInput, { value: input, onChange: setInput, onSubmit: handleSubmit, placeholder: "Enter your prompt" })] })); }; export default App; //# sourceMappingURL=ink-app.js.map