this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

refactor(scripts): convert setup script to verification tool

- Remove provider creation (now uses OPENAI_API_BASE env var)
- Add model listing with Claude model detection
- Add agent creation workflow test
- Test full proxy chain with message send

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

alice 4a43d2bb cb0c0d91

+97 -90
+97 -90
scripts/setup-letta-provider.ts
··· 1 1 #!/usr/bin/env bun 2 2 /** 3 - * Setup script to add anthropic-proxy as a custom LLM provider in Letta 3 + * Verification script to check Letta + LiteLLM + anthropic-proxy setup 4 4 * 5 - * This configures Letta to use the anthropic-proxy service, which provides 6 - * an OpenAI-compatible API that proxies to Anthropic's Claude models. 5 + * This verifies that: 6 + * 1. Letta is running and healthy 7 + * 2. Claude models are available via LiteLLM (configured via OPENAI_API_BASE) 8 + * 3. The proxy chain is working 7 9 * 8 10 * Run: bun scripts/setup-letta-provider.ts 9 11 */ 10 12 11 13 const LETTA_BASE_URL = process.env['LETTA_BASE_URL'] ?? 'http://localhost:8283'; 12 - const SESSION_ID = process.env['ANTHROPIC_PROXY_SESSION_ID'] ?? ''; 13 14 14 - // Validate session ID is set 15 - if (SESSION_ID === '') { 16 - console.error('Error: ANTHROPIC_PROXY_SESSION_ID is not set in .env'); 17 - console.error('Complete the OAuth flow first: http://localhost:4001/auth/device'); 18 - process.exit(1); 15 + interface Model { 16 + handle: string; 17 + provider_name: string; 18 + model_endpoint?: string; 19 19 } 20 20 21 - // The anthropic-proxy uses the session ID as the x-api-key header 22 - // Letta will pass this as Authorization: Bearer <api_key> which the proxy accepts 23 - const PROVIDER_CONFIG = { 24 - name: 'claude-proxy', // Using different name to avoid Letta soft-delete constraint issues 25 - provider_type: 'openai', // OpenAI-compatible API 26 - api_key: SESSION_ID, // Session ID is used as the API key 27 - base_url: 'http://anthropic-proxy:4001/v1', // Docker internal network 28 - }; 29 - 30 - interface Provider { 31 - id: string; 32 - name: string; 33 - api_key?: string; 21 + async function listModels(): Promise<Model[]> { 22 + const response = await fetch(`${LETTA_BASE_URL}/v1/models/`); 23 + if (!response.ok) { 24 + throw new Error(`Failed to fetch models: ${response.status}`); 25 + } 26 + return (await response.json()) as Model[]; 34 27 } 35 28 36 - async function getExistingProvider(): Promise<Provider | null> { 37 - try { 38 - const response = await fetch(`${LETTA_BASE_URL}/v1/providers/`); 39 - if (!response.ok) { 40 - return null; 41 - } 42 - const providers = (await response.json()) as Provider[]; 43 - return providers.find((p) => p.name === PROVIDER_CONFIG.name) ?? null; 44 - } catch { 45 - return null; 29 + async function testAgentCreation(): Promise<boolean> { 30 + // Create a test agent with letta-free, then update to Claude 31 + console.log('Testing agent creation workflow...'); 32 + 33 + // Step 1: Create agent with letta-free 34 + const createResponse = await fetch(`${LETTA_BASE_URL}/v1/agents/`, { 35 + method: 'POST', 36 + headers: { 'Content-Type': 'application/json' }, 37 + body: JSON.stringify({ 38 + name: `test-setup-${Date.now().toString()}`, 39 + model: 'letta/letta-free', 40 + embedding: 'letta/letta-free', 41 + memory_blocks: [{ label: 'persona', value: 'Test agent' }], 42 + }), 43 + }); 44 + 45 + if (!createResponse.ok) { 46 + console.error(' Failed to create test agent:', await createResponse.text()); 47 + return false; 46 48 } 47 - } 48 49 49 - async function updateProvider(providerId: string): Promise<boolean> { 50 - console.log(`Updating provider ${providerId} with new API key...`); 51 - const response = await fetch(`${LETTA_BASE_URL}/v1/providers/${providerId}`, { 50 + const agent = (await createResponse.json()) as { id: string }; 51 + console.log(` Created test agent: ${agent.id}`); 52 + 53 + // Step 2: Update to Claude 54 + const updateResponse = await fetch(`${LETTA_BASE_URL}/v1/agents/${agent.id}`, { 52 55 method: 'PATCH', 53 56 headers: { 'Content-Type': 'application/json' }, 54 - body: JSON.stringify({ api_key: SESSION_ID }), 57 + body: JSON.stringify({ 58 + llm_config: { 59 + handle: 'openai/claude-opus-4-5-20251101', 60 + model: 'claude-opus-4-5-20251101', 61 + model_endpoint_type: 'openai', 62 + model_endpoint: 'http://litellm:4000', 63 + context_window: 200000, 64 + temperature: 0.7, 65 + }, 66 + }), 55 67 }); 56 - if (!response.ok) { 57 - const errorText = await response.text(); 58 - console.warn(`Warning: Could not update provider: ${errorText}`); 68 + 69 + if (!updateResponse.ok) { 70 + console.error(' Failed to update agent to Claude:', await updateResponse.text()); 71 + // Clean up 72 + await fetch(`${LETTA_BASE_URL}/v1/agents/${agent.id}`, { method: 'DELETE' }); 59 73 return false; 60 74 } 61 - return true; 62 - } 63 75 64 - async function createProvider(): Promise<void> { 65 - console.log('Adding anthropic-proxy provider to Letta...'); 66 - console.log(` Letta URL: ${LETTA_BASE_URL}`); 67 - console.log(` Provider: ${PROVIDER_CONFIG.name}`); 68 - console.log(` Type: ${PROVIDER_CONFIG.provider_type}`); 69 - console.log(` Base URL: ${PROVIDER_CONFIG.base_url}`); 76 + console.log(' Updated agent to use Claude Opus 4.5'); 70 77 71 - const response = await fetch(`${LETTA_BASE_URL}/v1/providers/`, { 78 + // Step 3: Send test message 79 + const messageResponse = await fetch(`${LETTA_BASE_URL}/v1/agents/${agent.id}/messages`, { 72 80 method: 'POST', 73 81 headers: { 'Content-Type': 'application/json' }, 74 - body: JSON.stringify(PROVIDER_CONFIG), 82 + body: JSON.stringify({ 83 + messages: [{ role: 'user', content: 'Say "Hello from Claude!" and nothing else.' }], 84 + }), 75 85 }); 76 86 77 - if (!response.ok) { 78 - const errorText = await response.text(); 79 - throw new Error(`Failed to create provider: ${response.status} ${errorText}`); 87 + if (!messageResponse.ok) { 88 + console.error(' Failed to send test message:', await messageResponse.text()); 89 + // Clean up 90 + await fetch(`${LETTA_BASE_URL}/v1/agents/${agent.id}`, { method: 'DELETE' }); 91 + return false; 80 92 } 81 93 82 - const result = (await response.json()) as { id: string; name: string }; 83 - console.log(`\nProvider created successfully!`); 84 - console.log(` ID: ${result.id}`); 85 - console.log(` Name: ${result.name}`); 86 - } 94 + console.log(' Test message sent successfully'); 87 95 88 - async function listModels(): Promise<void> { 89 - console.log('\nAvailable models:'); 90 - const response = await fetch(`${LETTA_BASE_URL}/v1/models/`); 91 - if (!response.ok) { 92 - console.log(' (Could not fetch models)'); 93 - return; 94 - } 95 - const models = (await response.json()) as Array<{ handle: string; provider_name: string }>; 96 - for (const model of models) { 97 - console.log(` - ${model.handle} (${model.provider_name})`); 98 - } 96 + // Clean up 97 + await fetch(`${LETTA_BASE_URL}/v1/agents/${agent.id}`, { method: 'DELETE' }); 98 + console.log(' Test agent cleaned up'); 99 + 100 + return true; 99 101 } 100 102 101 103 async function main(): Promise<void> { 102 - console.log('=== Letta Provider Setup ===\n'); 104 + console.log('=== Letta Setup Verification ===\n'); 103 105 104 106 // Check if Letta is running 105 107 try { ··· 108 110 throw new Error('Letta health check failed'); 109 111 } 110 112 console.log('Letta is running.\n'); 111 - } catch (error) { 113 + } catch { 112 114 console.error('Error: Letta is not accessible at', LETTA_BASE_URL); 113 115 console.error('Make sure to run: docker compose up -d'); 114 116 process.exit(1); 115 117 } 116 118 117 - // Check if provider already exists 118 - const existing = await getExistingProvider(); 119 - if (existing !== null) { 120 - console.log(`Provider "anthropic-proxy" already exists (ID: ${existing.id})`); 121 - const updated = await updateProvider(existing.id); 122 - if (updated) { 123 - console.log('Provider updated successfully with current session ID.'); 124 - await listModels(); 125 - console.log('\nSetup complete! You can now run: bun run dev'); 126 - return; 127 - } 128 - console.log('Update failed, provider may already have correct configuration.'); 129 - await listModels(); 130 - return; 119 + // List available models 120 + console.log('Available models:'); 121 + const models = await listModels(); 122 + const claudeModels = models.filter((m) => m.handle.includes('claude')); 123 + 124 + for (const model of models) { 125 + const marker = model.handle.includes('claude') ? ' <-- Claude via LiteLLM' : ''; 126 + console.log(` - ${model.handle} (${model.provider_name})${marker}`); 131 127 } 132 128 133 - // Create the provider 134 - await createProvider(); 135 - await listModels(); 129 + if (claudeModels.length === 0) { 130 + console.error('\nNo Claude models found. Check OPENAI_API_BASE and LiteLLM configuration.'); 131 + process.exit(1); 132 + } 133 + 134 + console.log(`\nFound ${claudeModels.length.toString()} Claude model(s) via LiteLLM.\n`); 135 + 136 + // Test agent creation workflow 137 + const success = await testAgentCreation(); 136 138 137 - console.log('\nSetup complete! You can now run: bun run dev'); 139 + if (success) { 140 + console.log('\nSetup verified! You can now run: bun run dev'); 141 + } else { 142 + console.error('\nSetup verification failed. Check the logs above.'); 143 + process.exit(1); 144 + } 138 145 } 139 146 140 147 main().catch((error: unknown) => { 141 - console.error('Setup failed:', error); 148 + console.error('Verification failed:', error); 142 149 process.exit(1); 143 150 });