feat: letta code

This commit is contained in:
cpacker
2025-10-24 21:19:24 -07:00
commit 70ac76040d
139 changed files with 15340 additions and 0 deletions

188
src/headless.ts Normal file
View File

@@ -0,0 +1,188 @@
import { parseArgs } from "node:util";
import { Letta } from "@letta-ai/letta-client";
import { getClient } from "./agent/client";
import { createAgent } from "./agent/create";
import { sendMessageStream } from "./agent/message";
import { createBuffers, toLines } from "./cli/helpers/accumulator";
import { safeJsonParseOr } from "./cli/helpers/safeJsonParse";
import { drainStream } from "./cli/helpers/stream";
import { loadSettings, updateSettings } from "./settings";
import { checkToolPermission, executeTool } from "./tools/manager";
export async function handleHeadlessCommand(argv: string[]) {
const settings = await loadSettings();
// Parse CLI args
const { values, positionals } = parseArgs({
args: argv,
options: {
continue: { type: "boolean", short: "c" },
agent: { type: "string", short: "a" },
},
strict: false,
allowPositionals: true,
});
// Get prompt from either positional args or stdin
let prompt = positionals.slice(2).join(" ");
// If no prompt provided as args, try reading from stdin
if (!prompt) {
// Check if stdin is available (piped input)
if (!process.stdin.isTTY) {
const chunks: Buffer[] = [];
for await (const chunk of process.stdin) {
chunks.push(chunk);
}
prompt = Buffer.concat(chunks).toString("utf-8").trim();
}
}
if (!prompt) {
console.error("Error: No prompt provided");
process.exit(1);
}
const client = getClient();
// Resolve agent (same logic as interactive mode)
let agent: Letta.AgentState | null = null;
const specifiedAgentId = values.agent as string | undefined;
const shouldContinue = values.continue as boolean | undefined;
if (specifiedAgentId) {
try {
agent = await client.agents.retrieve(specifiedAgentId);
} catch (_error) {
console.error(`Agent ${specifiedAgentId} not found, creating new one...`);
}
}
if (!agent && shouldContinue && settings.lastAgent) {
try {
agent = await client.agents.retrieve(settings.lastAgent);
} catch (_error) {
console.error(
`Previous agent ${settings.lastAgent} not found, creating new one...`,
);
}
}
if (!agent) {
agent = await createAgent();
await updateSettings({ lastAgent: agent.id });
}
// Create buffers to accumulate stream
const buffers = createBuffers();
// Send message and process stream loop
let currentInput: Array<Letta.MessageCreate | Letta.ApprovalCreate> = [
{
role: Letta.MessageCreateRole.User,
content: [{ type: "text", text: prompt }],
},
];
try {
while (true) {
const stream = await sendMessageStream(agent.id, currentInput);
// Drain stream and collect approval requests
const { stopReason, approval } = await drainStream(
stream,
buffers,
() => {}, // No UI refresh needed in headless mode
);
// Case 1: Turn ended normally
if (stopReason === Letta.StopReasonType.EndTurn) {
break;
}
// Case 2: Requires approval
if (stopReason === Letta.StopReasonType.RequiresApproval) {
if (!approval) {
console.error("Unexpected null approval");
process.exit(1);
}
const { toolCallId, toolName, toolArgs } = approval;
// Check permission using existing permission system
const parsedArgs = safeJsonParseOr<Record<string, unknown>>(
toolArgs,
{},
);
const permission = await checkToolPermission(toolName, parsedArgs);
// Handle deny decision
if (permission.decision === "deny") {
const denyReason = `Permission denied: ${permission.matchedRule || permission.reason}`;
currentInput = [
{
type: "approval",
approvalRequestId: toolCallId,
approve: false,
reason: denyReason,
},
];
continue;
}
// Handle ask decision - in headless mode, auto-deny
if (permission.decision === "ask") {
currentInput = [
{
type: "approval",
approvalRequestId: toolCallId,
approve: false,
reason: "Tool requires approval (headless mode)",
},
];
continue;
}
// Permission is "allow" - auto-execute tool and continue loop
const toolResult = await executeTool(toolName, parsedArgs);
currentInput = [
{
type: "approval",
approvals: [
{
type: "tool",
toolCallId,
toolReturn: toolResult.toolReturn,
status: toolResult.status,
stdout: toolResult.stdout,
stderr: toolResult.stderr,
},
],
},
];
continue;
}
// Unexpected stop reason
console.error(`Unexpected stop reason: ${stopReason}`);
process.exit(1);
}
} catch (error) {
console.error(`Error: ${error}`);
process.exit(1);
}
// Extract final assistant message
const lines = toLines(buffers);
const lastAssistant = [...lines]
.reverse()
.find((line) => line.kind === "assistant");
if (lastAssistant && "text" in lastAssistant) {
console.log(lastAssistant.text);
} else {
console.error("No assistant response found");
process.exit(1);
}
}