feat: migrate to Letta TS SDK v1 (alpha) (#11)
This commit is contained in:
@@ -1,7 +1,8 @@
|
||||
// src/agent/check-approval.ts
|
||||
// Check for pending approvals and retrieve recent message history when resuming an agent
|
||||
|
||||
import type { Letta, LettaClient } from "@letta-ai/letta-client";
|
||||
import type Letta from "@letta-ai/letta-client";
|
||||
import type { LettaMessageUnion } from "@letta-ai/letta-client/resources/agents/messages";
|
||||
import type { ApprovalRequest } from "../cli/helpers/stream";
|
||||
|
||||
// Number of recent messages to backfill when resuming a session
|
||||
@@ -9,7 +10,7 @@ const MESSAGE_HISTORY_LIMIT = 15;
|
||||
|
||||
export interface ResumeData {
|
||||
pendingApproval: ApprovalRequest | null;
|
||||
messageHistory: Letta.LettaMessageUnion[];
|
||||
messageHistory: LettaMessageUnion[];
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -21,11 +22,12 @@ export interface ResumeData {
|
||||
* @returns Pending approval (if any) and recent message history
|
||||
*/
|
||||
export async function getResumeData(
|
||||
client: LettaClient,
|
||||
client: Letta,
|
||||
agentId: string,
|
||||
): Promise<ResumeData> {
|
||||
try {
|
||||
const messages = await client.agents.messages.list(agentId);
|
||||
const messagesPage = await client.agents.messages.list(agentId);
|
||||
const messages = messagesPage.items;
|
||||
if (!messages || messages.length === 0) {
|
||||
return { pendingApproval: null, messageHistory: [] };
|
||||
}
|
||||
@@ -33,14 +35,25 @@ export async function getResumeData(
|
||||
// Check for pending approval (last message)
|
||||
let pendingApproval: ApprovalRequest | null = null;
|
||||
const lastMessage = messages[messages.length - 1];
|
||||
if (lastMessage?.messageType === "approval_request_message") {
|
||||
const approvalMessage = lastMessage as Letta.ApprovalRequestMessage;
|
||||
const toolCall = approvalMessage.toolCall;
|
||||
pendingApproval = {
|
||||
toolCallId: toolCall.toolCallId || "",
|
||||
toolName: toolCall.name || "",
|
||||
toolArgs: toolCall.arguments || "",
|
||||
};
|
||||
if (lastMessage?.message_type === "approval_request_message") {
|
||||
// Use tool_calls array (new) or fallback to tool_call (deprecated)
|
||||
const toolCalls = Array.isArray(lastMessage.tool_calls)
|
||||
? lastMessage.tool_calls
|
||||
: lastMessage.tool_call
|
||||
? [lastMessage.tool_call]
|
||||
: [];
|
||||
|
||||
if (toolCalls.length > 0) {
|
||||
const toolCall = toolCalls[0];
|
||||
// Ensure all required fields are present (type guard for ToolCall vs ToolCallDelta)
|
||||
if (toolCall?.tool_call_id && toolCall.name && toolCall.arguments) {
|
||||
pendingApproval = {
|
||||
toolCallId: toolCall.tool_call_id,
|
||||
toolName: toolCall.name,
|
||||
toolArgs: toolCall.arguments,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get last N messages for backfill
|
||||
@@ -48,7 +61,7 @@ export async function getResumeData(
|
||||
let messageHistory = messages.slice(-historyCount);
|
||||
|
||||
// Skip if starts with orphaned tool_return (incomplete turn)
|
||||
if (messageHistory[0]?.messageType === "tool_return_message") {
|
||||
if (messageHistory[0]?.message_type === "tool_return_message") {
|
||||
messageHistory = messageHistory.slice(1);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { LettaClient } from "@letta-ai/letta-client";
|
||||
import Letta from "@letta-ai/letta-client";
|
||||
import { loadSettings, updateSettings } from "../settings";
|
||||
|
||||
export async function getClient() {
|
||||
const settings = await loadSettings();
|
||||
|
||||
const token = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY;
|
||||
if (!token) {
|
||||
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:",
|
||||
@@ -14,7 +14,7 @@ export async function getClient() {
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const baseUrl =
|
||||
const baseURL =
|
||||
process.env.LETTA_BASE_URL ||
|
||||
settings.env?.LETTA_BASE_URL ||
|
||||
"https://api.letta.com";
|
||||
@@ -37,5 +37,5 @@ export async function getClient() {
|
||||
await updateSettings({ env: updatedEnv });
|
||||
}
|
||||
|
||||
return new LettaClient({ token, baseUrl });
|
||||
return new Letta({ apiKey, baseURL });
|
||||
}
|
||||
|
||||
@@ -2,7 +2,11 @@
|
||||
* Utilities for creating an agent on the Letta API backend
|
||||
**/
|
||||
|
||||
import { Letta } from "@letta-ai/letta-client";
|
||||
import type {
|
||||
AgentType,
|
||||
Block,
|
||||
CreateBlock,
|
||||
} from "@letta-ai/letta-client/resources/agents/agents";
|
||||
import {
|
||||
loadProjectSettings,
|
||||
updateProjectSettings,
|
||||
@@ -39,7 +43,7 @@ export async function createAgent(
|
||||
const localSharedBlockIds = projectSettings.localSharedBlockIds;
|
||||
|
||||
// Retrieve existing blocks (both global and local) and match them with defaults
|
||||
const existingBlocks = new Map<string, Letta.Block>();
|
||||
const existingBlocks = new Map<string, Block>();
|
||||
|
||||
// Load global blocks (persona, human)
|
||||
for (const [label, blockId] of Object.entries(globalSharedBlockIds)) {
|
||||
@@ -69,7 +73,7 @@ export async function createAgent(
|
||||
|
||||
// Separate blocks into existing (reuse) and new (create)
|
||||
const blockIds: string[] = [];
|
||||
const blocksToCreate: Array<{ block: Letta.CreateBlock; label: string }> = [];
|
||||
const blocksToCreate: Array<{ block: CreateBlock; label: string }> = [];
|
||||
|
||||
for (const defaultBlock of defaultMemoryBlocks) {
|
||||
const existingBlock = existingBlocks.get(defaultBlock.label);
|
||||
@@ -131,17 +135,17 @@ export async function createAgent(
|
||||
|
||||
// Create agent with all block IDs (existing + newly created)
|
||||
const agent = await client.agents.create({
|
||||
agentType: Letta.AgentType.LettaV1Agent,
|
||||
agent_type: "letta_v1_agent" as AgentType,
|
||||
system: SYSTEM_PROMPT,
|
||||
name,
|
||||
model,
|
||||
contextWindowLimit: 200_000,
|
||||
context_window_limit: 200_000,
|
||||
tools: toolNames,
|
||||
blockIds,
|
||||
block_ids: blockIds,
|
||||
// should be default off, but just in case
|
||||
includeBaseTools: false,
|
||||
includeBaseToolRules: false,
|
||||
initialMessageSequence: [],
|
||||
include_base_tools: false,
|
||||
include_base_tool_rules: false,
|
||||
initial_message_sequence: [],
|
||||
});
|
||||
return agent; // { id, ... }
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
* Loads memory blocks from .mdx files in src/agent/prompts
|
||||
*/
|
||||
|
||||
import type { Letta } from "@letta-ai/letta-client";
|
||||
import type { CreateBlock } from "@letta-ai/letta-client/resources/agents/agents";
|
||||
import { MEMORY_PROMPTS } from "./promptAssets";
|
||||
|
||||
/**
|
||||
@@ -40,8 +40,8 @@ function parseMdxFrontmatter(content: string): {
|
||||
/**
|
||||
* Load memory blocks from .mdx files in src/agent/prompts
|
||||
*/
|
||||
async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
|
||||
const memoryBlocks: Letta.CreateBlock[] = [];
|
||||
async function loadMemoryBlocksFromMdx(): Promise<CreateBlock[]> {
|
||||
const memoryBlocks: CreateBlock[] = [];
|
||||
|
||||
const mdxFiles = ["persona.mdx", "human.mdx", "project.mdx"];
|
||||
// const mdxFiles = ["persona.mdx", "human.mdx", "style.mdx"];
|
||||
@@ -56,7 +56,7 @@ async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
|
||||
}
|
||||
const { frontmatter, body } = parseMdxFrontmatter(content);
|
||||
|
||||
const block: Letta.CreateBlock = {
|
||||
const block: CreateBlock = {
|
||||
label: frontmatter.label || filename.replace(".mdx", ""),
|
||||
value: body,
|
||||
};
|
||||
@@ -75,12 +75,12 @@ async function loadMemoryBlocksFromMdx(): Promise<Letta.CreateBlock[]> {
|
||||
}
|
||||
|
||||
// Cache for loaded memory blocks
|
||||
let cachedMemoryBlocks: Letta.CreateBlock[] | null = null;
|
||||
let cachedMemoryBlocks: CreateBlock[] | null = null;
|
||||
|
||||
/**
|
||||
* Get default starter memory blocks for new agents
|
||||
*/
|
||||
export async function getDefaultMemoryBlocks(): Promise<Letta.CreateBlock[]> {
|
||||
export async function getDefaultMemoryBlocks(): Promise<CreateBlock[]> {
|
||||
if (!cachedMemoryBlocks) {
|
||||
cachedMemoryBlocks = await loadMemoryBlocksFromMdx();
|
||||
}
|
||||
|
||||
@@ -2,22 +2,27 @@
|
||||
* Utilities for sending messages to an agent
|
||||
**/
|
||||
|
||||
import type { Letta } from "@letta-ai/letta-client";
|
||||
import type { Stream } from "@letta-ai/letta-client/core/streaming";
|
||||
import type { MessageCreate } from "@letta-ai/letta-client/resources/agents/agents";
|
||||
import type {
|
||||
ApprovalCreate,
|
||||
LettaStreamingResponse,
|
||||
} from "@letta-ai/letta-client/resources/agents/messages";
|
||||
import { getClient } from "./client";
|
||||
|
||||
export async function sendMessageStream(
|
||||
agentId: string,
|
||||
messages: Array<Letta.MessageCreate | Letta.ApprovalCreate>,
|
||||
messages: Array<MessageCreate | ApprovalCreate>,
|
||||
opts: {
|
||||
streamTokens?: boolean;
|
||||
background?: boolean;
|
||||
// add more later: includePings, request timeouts, etc.
|
||||
} = { streamTokens: true, background: true },
|
||||
): Promise<AsyncIterable<Letta.LettaStreamingResponse>> {
|
||||
): Promise<Stream<LettaStreamingResponse>> {
|
||||
const client = await getClient();
|
||||
return client.agents.messages.createStream(agentId, {
|
||||
return client.agents.messages.stream(agentId, {
|
||||
messages: messages,
|
||||
streamTokens: opts.streamTokens ?? true,
|
||||
stream_tokens: opts.streamTokens ?? true,
|
||||
background: opts.background ?? true,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
// src/agent/modify.ts
|
||||
// Utilities for modifying agent configuration
|
||||
|
||||
import type { Letta } from "@letta-ai/letta-client";
|
||||
import type { LlmConfig } from "@letta-ai/letta-client/resources/models/models";
|
||||
import { getClient } from "./client";
|
||||
|
||||
/**
|
||||
@@ -19,27 +19,27 @@ export async function updateAgentLLMConfig(
|
||||
agentId: string,
|
||||
modelHandle: string,
|
||||
updateArgs?: Record<string, unknown>,
|
||||
): Promise<Letta.LlmConfig> {
|
||||
): Promise<LlmConfig> {
|
||||
const client = await getClient();
|
||||
|
||||
// Step 1: Update model (top-level field)
|
||||
await client.agents.modify(agentId, { model: modelHandle });
|
||||
await client.agents.update(agentId, { model: modelHandle });
|
||||
|
||||
// Step 2: Get updated agent to retrieve current llmConfig
|
||||
// Step 2: Get updated agent to retrieve current llm_config
|
||||
const agent = await client.agents.retrieve(agentId);
|
||||
let finalConfig = agent.llmConfig;
|
||||
let finalConfig = agent.llm_config;
|
||||
|
||||
// Step 3: If we have updateArgs, merge them into llmConfig and patch again
|
||||
// Step 3: If we have updateArgs, merge them into llm_config and patch again
|
||||
if (updateArgs && Object.keys(updateArgs).length > 0) {
|
||||
const updatedLlmConfig = {
|
||||
...finalConfig,
|
||||
...updateArgs,
|
||||
} as Letta.LlmConfig;
|
||||
await client.agents.modify(agentId, { llmConfig: updatedLlmConfig });
|
||||
} as LlmConfig;
|
||||
await client.agents.update(agentId, { llm_config: updatedLlmConfig });
|
||||
|
||||
// Retrieve final state
|
||||
const finalAgent = await client.agents.retrieve(agentId);
|
||||
finalConfig = finalAgent.llmConfig;
|
||||
finalConfig = finalAgent.llm_config;
|
||||
}
|
||||
|
||||
return finalConfig;
|
||||
|
||||
Reference in New Issue
Block a user