feat: add toolset flag (#116)

This commit is contained in:
Charles Packer
2025-11-23 20:13:02 -08:00
committed by GitHub
parent 592ed66e1b
commit d002c75f9c
3 changed files with 68 additions and 22 deletions

View File

@@ -168,6 +168,7 @@ letta --agent <id> --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 <id> --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

View File

@@ -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

View File

@@ -31,6 +31,7 @@ OPTIONS
-c, --continue Resume previous session (uses global lastAgent, deprecated)
-a, --agent <id> Use a specific agent ID
-m, --model <id> Model ID or handle (e.g., "opus" or "anthropic/claude-opus-4-1-20250805")
--toolset <name> Force toolset: "codex" or "default" (overrides model-based auto-selection)
-p, --prompt Headless prompt mode
--output-format <fmt> 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,
}),
{