// The Letta API client — letta's own code, living in the module (novox/hq ADR 0039). Its tools // import it; nothing outside letta does. // // Letta authenticates with a single server password, presented as a Bearer token. That password is // a mesh own-secret, minted once and handed to both the server (LETTA_SERVER_PASSWORD) and this // client (MESH_LETTA_PASSWORD) — so the module's tools are live without anything configured by hand. // The runtime config file may still override the URL or password. import { readFileSync } from "node:fs"; export interface LettaAgent { id: string; name: string; } export interface LettaMessage { id?: string; role?: string; text?: string; [key: string]: unknown; } function meshConfig(file?: string): Record { if (!file) return {}; try { return JSON.parse(readFileSync(file, "utf8")) as Record; } catch { return {}; } } export class LettaClient { readonly baseUrl: string; constructor( url: string, private readonly password: string, ) { this.baseUrl = url.replace(/\/+$/, ""); } /** * Build from the module's resolved environment. URL and password come from the runtime config * file first (MESH_LETTA_CONFIG_FILE), then the mesh's own names, then the bare LETTA_* names. A * password is required — Letta rejects unauthenticated calls when SECURE is on — so this throws * rather than hand back a client that fails on first use. */ static fromEnv(env: NodeJS.ProcessEnv = process.env): LettaClient { const cfg = meshConfig(env.MESH_LETTA_CONFIG_FILE); const url = cfg.url ?? env.MESH_LETTA_URL ?? env.LETTA_URL ?? "http://127.0.0.1:8283"; const password = cfg.password ?? env.MESH_LETTA_PASSWORD ?? env.LETTA_SERVER_PASSWORD; if (!password) throw new Error("no Letta password — set MESH_LETTA_PASSWORD"); return new LettaClient(url, password); } private async request(path: string, options: RequestInit = {}): Promise { const res = await fetch(`${this.baseUrl}${path}`, { ...options, headers: { "Content-Type": "application/json", Authorization: `Bearer ${this.password}`, ...(options.headers as Record | undefined), }, }); if (!res.ok) throw new Error(`letta ${path}: ${res.status} ${await res.text()}`); if (res.status === 204) return null as T; const text = await res.text(); return (text ? JSON.parse(text) : null) as T; } async listAgents(): Promise { return (await this.request(`/v1/agents/`)) ?? []; } async getMessages(agentId: string, limit = 20): Promise { return (await this.request(`/v1/agents/${agentId}/messages?limit=${limit}`)) ?? []; } async sendMessage(agentId: string, text: string): Promise { return this.request(`/v1/agents/${agentId}/messages`, { method: "POST", body: JSON.stringify({ messages: [{ role: "user", content: text }] }), }); } }