this repo has no description
1/**
2 * Letta client and provider bootstrap module
3 *
4 * Provides:
5 * - Singleton Letta client instance
6 * - Anthropic provider creation/management
7 * - Agent creation (placeholder for M1)
8 */
9
10import { Letta } from '@letta-ai/letta-client';
11import { config } from './config';
12import { getAllLettaToolsCreate } from './tools';
13
14/**
15 * Singleton Letta client instance
16 */
17let lettaClient: Letta | null = null;
18
19/**
20 * Map of tool name -> Letta tool ID
21 * Populated during initialization
22 */
23const registeredToolIds = new Map<string, string>();
24
25/**
26 * Get all registered tool IDs for attaching to agents
27 */
28export function getRegisteredToolIds(): string[] {
29 return Array.from(registeredToolIds.values());
30}
31
32/**
33 * Get or create the Letta client singleton
34 */
35export function getLettaClient(): Letta {
36 lettaClient ??= new Letta({
37 baseURL: config.LETTA_BASE_URL,
38 apiKey: config.LETTA_SERVER_PASSWORD || undefined,
39 });
40 return lettaClient;
41}
42
43/**
44 * Ensure the Anthropic provider is configured
45 *
46 * NOTE: The current @letta-ai/letta-client SDK (v1.3.3) doesn't expose a
47 * `providers` API. Models are specified directly in the agent configuration
48 * using the format "provider/model-name" (e.g., "anthropic/claude-opus-4-5-20251101").
49 *
50 * This function verifies Letta connectivity by listing available models.
51 * Provider configuration (API keys, base URLs) is handled via environment
52 * variables or Letta's server configuration, not the client SDK.
53 *
54 * For the anthropic-proxy setup:
55 * - The proxy should be configured as an Anthropic provider in Letta server
56 * - This is typically done via Letta's admin interface or configuration files
57 * - The client SDK just references models by their provider/name handle
58 *
59 * @returns A status message
60 */
61export async function ensureProvider(): Promise<string> {
62 const client = getLettaClient();
63
64 try {
65 console.log('Verifying Letta connectivity...');
66
67 // List available models to verify connectivity
68 const llmModels = await client.models.list();
69 const embeddingModels = await client.models.embeddings.list();
70 console.log(
71 `Letta is accessible. Found ${llmModels.length.toString()} LLM models and ${embeddingModels.length.toString()} embedding models.`
72 );
73
74 // Log available Claude models (via LiteLLM/openai-proxy or native Anthropic)
75 const claudeModels = llmModels.filter(
76 (m) =>
77 m.provider_type === 'anthropic' ||
78 (m.provider_name?.includes('litellm') ?? false) ||
79 (m.handle?.includes('claude') ?? false) ||
80 (m.handle?.includes('openai-proxy') ?? false) ||
81 m.name.includes('claude')
82 );
83
84 if (claudeModels.length > 0) {
85 console.log(`Found ${claudeModels.length.toString()} Claude model(s):`);
86 claudeModels.forEach((m) => {
87 console.log(` - ${m.handle ?? m.name}`);
88 });
89 } else {
90 console.warn(
91 '⚠️ No Claude models found. ' +
92 'Make sure the anthropic-proxy is configured as a provider in Letta server. ' +
93 'Run: bun run setup:letta'
94 );
95 }
96
97 return 'Letta connectivity verified';
98 } catch (error: unknown) {
99 console.error('Failed to verify Letta connectivity:', error);
100 throw error;
101 }
102}
103
104/**
105 * Register all tools with Letta
106 *
107 * Creates tools in Letta if they don't exist, or updates existing ones.
108 * Tool IDs are stored for later attachment to agents.
109 */
110async function registerTools(): Promise<void> {
111 const client = getLettaClient();
112 const toolDefs = getAllLettaToolsCreate();
113
114 console.log(`Registering ${String(toolDefs.length)} tools with Letta...`);
115
116 // Build a set of existing tool names to avoid duplicates
117 const existingTools = new Map<string, string>();
118 for await (const tool of client.tools.list()) {
119 if (tool.name !== null && tool.name !== undefined) {
120 existingTools.set(tool.name, tool.id);
121 }
122 }
123
124 for (const def of toolDefs) {
125 try {
126 // Extract expected tool name from Python source code (function name)
127 const funcMatch = /^def\s+(\w+)\s*\(/m.exec(def.source_code);
128 const expectedName = funcMatch?.[1] ?? 'unknown';
129
130 // Check if tool already exists
131 const existingId = existingTools.get(expectedName);
132 if (existingId !== undefined) {
133 // Update existing tool with new source code and json_schema
134 await client.tools.update(existingId, {
135 source_code: def.source_code,
136 description: def.description ?? null,
137 json_schema: def.json_schema ?? null,
138 });
139 console.log(` Updated tool '${expectedName}' (${existingId})`);
140 registeredToolIds.set(expectedName, existingId);
141 } else {
142 // Create new tool - Letta extracts name from source_code
143 const created = await client.tools.create(def);
144 const createdName = created.name ?? expectedName;
145 console.log(` Created tool '${createdName}' (${created.id})`);
146 registeredToolIds.set(createdName, created.id);
147 }
148 } catch (error: unknown) {
149 const errorMessage = error instanceof Error ? error.message : 'Unknown error';
150 console.error(` Failed to register tool:`, errorMessage);
151 // Continue with other tools
152 }
153 }
154
155 console.log(`Registered ${String(registeredToolIds.size)} tools`);
156}
157
158/**
159 * Get or create the ADHD assistant agent
160 *
161 * This is a placeholder for M1 implementation.
162 * For now, it just logs a message and returns null.
163 *
164 * @returns Agent ID once implemented, null for now
165 */
166export function getOrCreateAgent(): Promise<string | null> {
167 console.log('getOrCreateAgent() called - placeholder for M1 implementation');
168 console.log('Agent creation will be implemented in milestone M1');
169 return Promise.resolve(null);
170}
171
172/**
173 * Initialize Letta on application startup
174 *
175 * - Creates the Letta client
176 * - Ensures the anthropic-proxy provider exists
177 * - Eventually will create/get the agent (M1)
178 */
179export async function initializeLetta(): Promise<void> {
180 console.log('Initializing Letta...');
181
182 // Get client (creates singleton)
183 getLettaClient();
184 console.log(`Letta client initialized (base URL: ${config.LETTA_BASE_URL})`);
185
186 // Ensure provider exists
187 try {
188 await ensureProvider();
189 } catch (error) {
190 console.error('Failed to ensure provider during initialization:', error);
191 throw error;
192 }
193
194 // Register tools with Letta
195 try {
196 await registerTools();
197 } catch (error) {
198 console.error('Failed to register tools during initialization:', error);
199 // Non-fatal - continue without tools
200 }
201
202 console.log('Letta initialization complete');
203}