fix: don't clobber tools on resume (#118)

This commit is contained in:
Charles Packer
2025-11-23 22:30:13 -08:00
committed by GitHub
parent 8cab132513
commit 4862a87fb1
4 changed files with 189 additions and 3 deletions

View File

@@ -215,6 +215,43 @@ export async function analyzeToolApproval(
return analyzeApprovalContext(toolName, toolArgs, workingDirectory);
}
/**
* Loads specific tools by name into the registry.
* Used when resuming an agent to load only the tools attached to that agent.
*
* @param toolNames - Array of specific tool names to load
*/
export async function loadSpecificTools(toolNames: string[]): Promise<void> {
for (const name of toolNames) {
// Skip if tool filter is active and this tool is not enabled
const { toolFilter } = await import("./filter");
if (!toolFilter.isEnabled(name)) {
continue;
}
const definition = TOOL_DEFINITIONS[name as ToolName];
if (!definition) {
console.warn(`Tool ${name} not found in definitions, skipping`);
continue;
}
if (!definition.impl) {
throw new Error(`Tool implementation not found for ${name}`);
}
const toolSchema: ToolSchema = {
name,
description: definition.description,
input_schema: definition.schema,
};
toolRegistry.set(name, {
schema: toolSchema,
fn: definition.impl,
});
}
}
/**
* Loads all tools defined in TOOL_NAMES and constructs their full schemas + function references.
* This should be called on program startup.

View File

@@ -1,3 +1,4 @@
import type Letta from "@letta-ai/letta-client";
import { getClient } from "../agent/client";
import { resolveModel } from "../agent/model";
import { linkToolsToAgent, unlinkToolsFromAgent } from "../agent/modify";
@@ -10,6 +11,81 @@ import {
upsertToolsToServer,
} from "./manager";
const CODEX_TOOLS = [
"shell_command",
"shell",
"read_file",
"list_dir",
"grep_files",
"apply_patch",
"update_plan",
];
const ANTHROPIC_TOOLS = [
"Bash",
"BashOutput",
"Edit",
"ExitPlanMode",
"Glob",
"Grep",
"KillBash",
"LS",
"MultiEdit",
"Read",
"TodoWrite",
"Write",
];
/**
* Gets the list of Letta Code tools currently attached to an agent.
* Returns the tool names that are both attached to the agent AND in our tool definitions.
*/
export async function getAttachedLettaTools(
client: Letta,
agentId: string,
): Promise<string[]> {
const agent = await client.agents.retrieve(agentId, {
include: ["agent.tools"],
});
const toolNames =
agent.tools
?.map((t) => t.name)
.filter((name): name is string => typeof name === "string") || [];
// Get all possible Letta Code tool names
const allLettaTools = [...CODEX_TOOLS, ...ANTHROPIC_TOOLS];
// Return intersection: tools that are both attached AND in our definitions
return toolNames.filter((name) => allLettaTools.includes(name));
}
/**
* Detects which toolset is attached to an agent by examining its tools.
* Returns "codex" if majority are codex tools, "default" if majority are anthropic tools,
* or null if no Letta Code tools are detected.
*/
export async function detectToolsetFromAgent(
client: Letta,
agentId: string,
): Promise<"codex" | "default" | null> {
const attachedTools = await getAttachedLettaTools(client, agentId);
if (attachedTools.length === 0) {
return null;
}
const codexCount = attachedTools.filter((name) =>
CODEX_TOOLS.includes(name),
).length;
const anthropicCount = attachedTools.filter((name) =>
ANTHROPIC_TOOLS.includes(name),
).length;
// Return whichever has more tools attached
return codexCount > anthropicCount ? "codex" : "default";
}
/**
* Force switch to a specific toolset regardless of model.
*