feat: add API key caching via settings.json (#16)
Co-authored-by: Letta <noreply@letta.com>
This commit is contained in:
@@ -1,13 +1,41 @@
|
||||
import { LettaClient } from "@letta-ai/letta-client";
|
||||
import { loadSettings, updateSettings } from "../settings";
|
||||
|
||||
export function getClient() {
|
||||
const token = process.env.LETTA_API_KEY;
|
||||
export async function getClient() {
|
||||
const settings = await loadSettings();
|
||||
|
||||
const token = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
|
||||
if (!token) {
|
||||
console.error("Missing LETTA_API_KEY");
|
||||
console.error(
|
||||
"Set it via environment variable or add it to ~/.letta/settings.json:",
|
||||
);
|
||||
console.error(' { "env": { "LETTA_API_KEY": "sk-let-..." } }');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseUrl = process.env.LETTA_BASE_URL || "https://api.letta.com";
|
||||
const baseUrl =
|
||||
process.env.LETTA_BASE_URL ||
|
||||
settings.env?.LETTA_BASE_URL ||
|
||||
"https://api.letta.com";
|
||||
|
||||
// Auto-cache: if env vars are set but not in settings, write them to settings
|
||||
let needsUpdate = false;
|
||||
const updatedEnv = { ...settings.env };
|
||||
|
||||
if (process.env.LETTA_API_KEY && !settings.env?.LETTA_API_KEY) {
|
||||
updatedEnv.LETTA_API_KEY = process.env.LETTA_API_KEY;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (process.env.LETTA_BASE_URL && !settings.env?.LETTA_BASE_URL) {
|
||||
updatedEnv.LETTA_BASE_URL = process.env.LETTA_BASE_URL;
|
||||
needsUpdate = true;
|
||||
}
|
||||
|
||||
if (needsUpdate) {
|
||||
await updateSettings({ env: updatedEnv });
|
||||
}
|
||||
|
||||
return new LettaClient({ token, baseUrl });
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ export async function createAgent(
|
||||
name = "letta-cli-agent",
|
||||
model = "anthropic/claude-sonnet-4-5-20250929",
|
||||
) {
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
|
||||
// Get loaded tool names (tools are already registered with Letta)
|
||||
const toolNames = [
|
||||
|
||||
@@ -14,7 +14,7 @@ export async function sendMessageStream(
|
||||
// add more later: includePings, request timeouts, etc.
|
||||
} = { streamTokens: true, background: true },
|
||||
): Promise<AsyncIterable<Letta.LettaStreamingResponse>> {
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
return client.agents.messages.createStream(agentId, {
|
||||
messages: messages,
|
||||
streamTokens: opts.streamTokens ?? true,
|
||||
|
||||
@@ -20,7 +20,7 @@ export async function updateAgentLLMConfig(
|
||||
modelHandle: string,
|
||||
updateArgs?: Record<string, unknown>,
|
||||
): Promise<Letta.LlmConfig> {
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
|
||||
// Step 1: Update model (top-level field)
|
||||
await client.agents.modify(agentId, { model: modelHandle });
|
||||
|
||||
@@ -343,7 +343,7 @@ export default function App({
|
||||
const fetchConfig = async () => {
|
||||
try {
|
||||
const { getClient } = await import("../agent/client");
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
setLlmConfig(agent.llmConfig);
|
||||
} catch (error) {
|
||||
@@ -541,7 +541,7 @@ export default function App({
|
||||
|
||||
setInterruptRequested(true);
|
||||
try {
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
|
||||
// Send cancel request to backend
|
||||
await client.agents.messages.cancel(agentId);
|
||||
@@ -746,7 +746,7 @@ export default function App({
|
||||
// Check for pending approvals before sending message
|
||||
if (CHECK_PENDING_APPROVALS_BEFORE_SEND) {
|
||||
try {
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
const { pendingApproval: existingApproval } = await getResumeData(
|
||||
client,
|
||||
agentId,
|
||||
|
||||
@@ -45,7 +45,7 @@ export async function handleHeadlessCommand(argv: string[]) {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
|
||||
// Resolve agent (same logic as interactive mode)
|
||||
let agent: Letta.AgentState | null = null;
|
||||
|
||||
10
src/index.ts
10
src/index.ts
@@ -104,9 +104,13 @@ async function main() {
|
||||
const isHeadless = values.prompt || values.run || !process.stdin.isTTY;
|
||||
|
||||
// Validate API key early before any UI rendering
|
||||
const apiKey = process.env.LETTA_API_KEY;
|
||||
const apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
|
||||
if (!apiKey) {
|
||||
console.error("Missing LETTA_API_KEY");
|
||||
console.error(
|
||||
"Set it via environment variable or add it to ~/.letta/settings.json:",
|
||||
);
|
||||
console.error(' { "env": { "LETTA_API_KEY": "sk-let-..." } }');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -158,7 +162,7 @@ async function main() {
|
||||
if (isHeadless) {
|
||||
// For headless mode, load tools synchronously
|
||||
await loadTools();
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
await upsertToolsToServer(client);
|
||||
|
||||
const { handleHeadlessCommand } = await import("./headless");
|
||||
@@ -193,7 +197,7 @@ async function main() {
|
||||
await loadTools();
|
||||
|
||||
setLoadingState("upserting");
|
||||
const client = getClient();
|
||||
const client = await getClient();
|
||||
await upsertToolsToServer(client);
|
||||
|
||||
setLoadingState("initializing");
|
||||
|
||||
@@ -14,6 +14,7 @@ export interface Settings {
|
||||
tokenStreaming: boolean;
|
||||
globalSharedBlockIds: Record<string, string>; // label -> blockId mapping (persona, human; style moved to project settings)
|
||||
permissions?: PermissionRules;
|
||||
env?: Record<string, string>;
|
||||
}
|
||||
|
||||
const DEFAULT_SETTINGS: Settings = {
|
||||
|
||||
Reference in New Issue
Block a user