this repo has no description
0
fork

Configure Feed

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

feat: add Letta provider setup script

- Add scripts/setup-letta-provider.ts to configure claude-proxy in Letta
- Update bot.ts to use claude-proxy/model format
- Add setup:letta npm script
- Update README with provider setup step

The proxy uses session ID as x-api-key header for authentication.

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

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

alice 2712f671 518d83f4

+161 -7
+15 -5
README.md
··· 65 65 ANTHROPIC_PROXY_SESSION_ID=your_session_id_here 66 66 ``` 67 67 68 - ### 5. Run the app 68 + ### 5. Configure Letta provider 69 + 70 + Add the anthropic-proxy as a custom LLM provider in Letta: 69 71 70 72 ```bash 71 - bun run src/index.ts 73 + bun run setup:letta 72 74 ``` 73 75 74 - Or with hot reload: 76 + This registers the proxy with Letta so it can use Claude models. 77 + 78 + ### 6. Run the app 75 79 76 80 ```bash 77 - bun --hot src/index.ts 81 + bun run dev 82 + ``` 83 + 84 + Or without hot reload: 85 + 86 + ```bash 87 + bun run start 78 88 ``` 79 89 80 90 ## Environment Variables ··· 188 198 ## Milestones 189 199 190 200 - [x] **M0**: Infrastructure (Docker, config, health, Letta client) 191 - - [ ] **M1**: E2E Chat (Telegram bot, basic message flow) 201 + - [x] **M1**: E2E Chat (Telegram bot, basic message flow) 192 202 - [ ] **M2**: Tools + Items (database, capture, breakdown) 193 203 - [ ] **M3**: Tone + Detection (overwhelm, self-bullying) 194 204 - [ ] **M4**: Tiny Wins (win tracking)
+1
package.json
··· 6 6 "scripts": { 7 7 "dev": "bun --hot src/index.ts", 8 8 "start": "bun src/index.ts", 9 + "setup:letta": "bun scripts/setup-letta-provider.ts", 9 10 "typecheck": "tsgo --noEmit", 10 11 "lint": "eslint src/", 11 12 "lint:fix": "eslint src/ --fix",
+143
scripts/setup-letta-provider.ts
··· 1 + #!/usr/bin/env bun 2 + /** 3 + * Setup script to add anthropic-proxy as a custom LLM provider in Letta 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. 7 + * 8 + * Run: bun scripts/setup-letta-provider.ts 9 + */ 10 + 11 + const LETTA_BASE_URL = process.env['LETTA_BASE_URL'] ?? 'http://localhost:8283'; 12 + const SESSION_ID = process.env['ANTHROPIC_PROXY_SESSION_ID'] ?? ''; 13 + 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); 19 + } 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; 34 + } 35 + 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; 46 + } 47 + } 48 + 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}`, { 52 + method: 'PATCH', 53 + headers: { 'Content-Type': 'application/json' }, 54 + body: JSON.stringify({ api_key: SESSION_ID }), 55 + }); 56 + if (!response.ok) { 57 + const errorText = await response.text(); 58 + console.warn(`Warning: Could not update provider: ${errorText}`); 59 + return false; 60 + } 61 + return true; 62 + } 63 + 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}`); 70 + 71 + const response = await fetch(`${LETTA_BASE_URL}/v1/providers/`, { 72 + method: 'POST', 73 + headers: { 'Content-Type': 'application/json' }, 74 + body: JSON.stringify(PROVIDER_CONFIG), 75 + }); 76 + 77 + if (!response.ok) { 78 + const errorText = await response.text(); 79 + throw new Error(`Failed to create provider: ${response.status} ${errorText}`); 80 + } 81 + 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 + } 87 + 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 + } 99 + } 100 + 101 + async function main(): Promise<void> { 102 + console.log('=== Letta Provider Setup ===\n'); 103 + 104 + // Check if Letta is running 105 + try { 106 + const health = await fetch(`${LETTA_BASE_URL}/v1/health/`); 107 + if (!health.ok) { 108 + throw new Error('Letta health check failed'); 109 + } 110 + console.log('Letta is running.\n'); 111 + } catch (error) { 112 + console.error('Error: Letta is not accessible at', LETTA_BASE_URL); 113 + console.error('Make sure to run: docker compose up -d'); 114 + process.exit(1); 115 + } 116 + 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; 131 + } 132 + 133 + // Create the provider 134 + await createProvider(); 135 + await listModels(); 136 + 137 + console.log('\nSetup complete! You can now run: bun run dev'); 138 + } 139 + 140 + main().catch((error: unknown) => { 141 + console.error('Setup failed:', error); 142 + process.exit(1); 143 + });
+2 -2
src/bot.ts
··· 56 56 const agentState = await client.agents.create({ 57 57 name: `user-${userId.toString()}-${usernameOrUnknown}`, 58 58 description: `ADHD support agent for Telegram user ${userId.toString()}`, 59 - // Using Claude Opus 4.5 (should be configured in Letta server) 60 - model: 'anthropic/claude-opus-4-5-20251101', 59 + // Using Claude Opus 4.5 via claude-proxy provider 60 + model: 'claude-proxy/claude-opus-4-5-20251101', 61 61 embedding: 'openai/text-embedding-ada-002', 62 62 // Memory blocks with system prompt for ADHD support 63 63 memory_blocks: [