diff --git a/README.md b/README.md index 3dc387f..29a061a 100644 --- a/README.md +++ b/README.md @@ -168,6 +168,7 @@ letta --agent --unlink # Remove Letta Code tools from agent, then start While in a session, you can use these commands: - `/agent` - Show current agent link - `/model` - Switch models +- `/toolset` - Switch toolsets (codex/default) - `/rename` - Rename the current agent - `/stream` - Toggle token streaming on/off - `/link` - Attach Letta Code tools to current agent (enables Read, Write, Edit, Bash, etc.) @@ -194,31 +195,32 @@ letta --agent --unlink # Remove Letta Code tools When you attach tools with `/link` or `--link`, they are added to the agent with approval rules enabled (human-in-the-loop). This means the agent can use these tools, but you'll be prompted to approve each tool call. Use permission modes to control approval behavior (see Permissions section below). -#### Available Tools +### Toolsets -Letta Code provides the following tools for filesystem and shell operations: +Letta Code includes different toolsets optimized for different model providers: -**File Operations:** -- **Read** - Read files from the filesystem, supports offset/limit for large files -- **Write** - Write or overwrite files, creates directories automatically -- **Edit** - Perform exact string replacements in files (single edit) -- **MultiEdit** - Perform multiple find-and-replace operations in a single file efficiently -- **LS** - List files and directories, supports ignore patterns +1. **Default Toolset** (Anthropic-optimized, best for Claude models) +2. **Codex Toolset** (OpenAI-optimized, best for GPT models) -**Search & Discovery:** -- **Glob** - Fast file pattern matching with glob patterns (`**/*.js`, `src/**/*.ts`) -- **Grep** - Powerful search using ripgrep, supports regex and various output modes +**Automatic Selection:** +When you specify a model, Letta Code automatically selects the appropriate toolset: +```bash +letta --model haiku # Loads default toolset +letta --model gpt-5-codex # Loads codex toolset +``` -**Shell Operations:** -- **Bash** - Execute shell commands in a persistent session with timeout support -- **BashOutput** - Retrieve output from background bash shells -- **KillBash** - Terminate running background bash shells +**Manual Override:** +You can force a specific toolset regardless of model: +```bash +# CLI flag (at startup) +letta --model haiku --toolset codex # Use Codex-style tools with Claude Haiku +letta --model gpt-5-codex --toolset default # Use Anthropic-style tools with GPT-5-Codex -**Task Management:** -- **TodoWrite** - Create and manage structured task lists for tracking progress -- **ExitPlanMode** - Signal completion of planning phase and readiness to implement +# Interactive command (during session) +/toolset # Opens toolset selector +``` -All tools support approval rules and permission modes for safe execution. See the Permissions section for details on controlling tool access. +The `/model` command automatically switches toolsets when you change models. Use `/toolset` if you want to manually override the automatic selection. ### Headless Mode ```bash diff --git a/src/headless.ts b/src/headless.ts index 4e2c914..7924913 100644 --- a/src/headless.ts +++ b/src/headless.ts @@ -35,6 +35,7 @@ export async function handleHeadlessCommand( new: { type: "boolean" }, agent: { type: "string", short: "a" }, model: { type: "string", short: "m" }, + toolset: { type: "string" }, prompt: { type: "boolean", short: "p" }, "output-format": { type: "string" }, // Additional flags from index.ts that need to be filtered out diff --git a/src/index.ts b/src/index.ts index 5c9b328..2957699 100755 --- a/src/index.ts +++ b/src/index.ts @@ -31,6 +31,7 @@ OPTIONS -c, --continue Resume previous session (uses global lastAgent, deprecated) -a, --agent Use a specific agent ID -m, --model Model ID or handle (e.g., "opus" or "anthropic/claude-opus-4-1-20250805") + --toolset Force toolset: "codex" or "default" (overrides model-based auto-selection) -p, --prompt Headless prompt mode --output-format Output format for headless mode (text, json, stream-json) Default: text @@ -70,6 +71,26 @@ EXAMPLES console.log(usage); } +/** + * Helper to determine which model identifier to pass to loadTools() + * based on user's model and/or toolset preferences. + */ +function getModelForToolLoading( + specifiedModel?: string, + specifiedToolset?: "codex" | "default", +): string | undefined { + // If toolset is explicitly specified, use a dummy model from that provider + // to trigger the correct toolset loading logic + if (specifiedToolset === "codex") { + return "openai/gpt-4"; + } + if (specifiedToolset === "default") { + return "anthropic/claude-sonnet-4"; + } + // Otherwise, use the specified model (or undefined for auto-detection) + return specifiedModel; +} + async function main() { // Initialize settings manager (loads settings once into memory) await settingsManager.initialize(); @@ -97,6 +118,7 @@ async function main() { "fresh-blocks": { type: "boolean" }, agent: { type: "string", short: "a" }, model: { type: "string", short: "m" }, + toolset: { type: "string" }, prompt: { type: "boolean", short: "p" }, run: { type: "boolean" }, tools: { type: "string" }, @@ -151,10 +173,23 @@ async function main() { const freshBlocks = (values["fresh-blocks"] as boolean | undefined) ?? false; const specifiedAgentId = (values.agent as string | undefined) ?? null; const specifiedModel = (values.model as string | undefined) ?? undefined; + const specifiedToolset = (values.toolset as string | undefined) ?? undefined; const skillsDirectory = (values.skills as string | undefined) ?? undefined; const sleeptimeFlag = (values.sleeptime as boolean | undefined) ?? undefined; const isHeadless = values.prompt || values.run || !process.stdin.isTTY; + // Validate toolset if provided + if ( + specifiedToolset && + specifiedToolset !== "codex" && + specifiedToolset !== "default" + ) { + console.error( + `Error: Invalid toolset "${specifiedToolset}". Must be "codex" or "default".`, + ); + process.exit(1); + } + // Check if API key is configured const apiKey = process.env.LETTA_API_KEY || settings.env?.LETTA_API_KEY; const baseURL = @@ -270,8 +305,12 @@ async function main() { } if (isHeadless) { - // For headless mode, load tools synchronously (respecting model when provided) - await loadTools(specifiedModel); + // For headless mode, load tools synchronously (respecting model/toolset when provided) + const modelForTools = getModelForToolLoading( + specifiedModel, + specifiedToolset as "codex" | "default" | undefined, + ); + await loadTools(modelForTools); const client = await getClient(); await upsertToolsToServer(client); @@ -293,6 +332,7 @@ async function main() { freshBlocks, agentIdArg, model, + toolset, skillsDirectory, }: { continueSession: boolean; @@ -300,6 +340,7 @@ async function main() { freshBlocks: boolean; agentIdArg: string | null; model?: string; + toolset?: "codex" | "default"; skillsDirectory?: string; }) { const [loadingState, setLoadingState] = useState< @@ -319,7 +360,8 @@ async function main() { useEffect(() => { async function init() { setLoadingState("assembling"); - await loadTools(model); + const modelForTools = getModelForToolLoading(model, toolset); + await loadTools(modelForTools); setLoadingState("upserting"); const client = await getClient(); @@ -499,6 +541,7 @@ async function main() { freshBlocks: freshBlocks, agentIdArg: specifiedAgentId, model: specifiedModel, + toolset: specifiedToolset as "codex" | "default" | undefined, skillsDirectory: skillsDirectory, }), {