feat: Add memory_apply_patch for non-Anthropic models (#149)
This commit is contained in:
@@ -89,14 +89,44 @@ export async function createAgent(
|
||||
getServerToolName(name),
|
||||
);
|
||||
|
||||
const baseMemoryTool = modelHandle.startsWith("anthropic/")
|
||||
? "memory"
|
||||
: "memory_apply_patch";
|
||||
const defaultBaseTools = baseTools ?? [
|
||||
"memory",
|
||||
baseMemoryTool,
|
||||
"web_search",
|
||||
"conversation_search",
|
||||
"fetch_webpage",
|
||||
];
|
||||
|
||||
const toolNames = [...serverToolNames, ...defaultBaseTools];
|
||||
let toolNames = [...serverToolNames, ...defaultBaseTools];
|
||||
|
||||
// Fallback: if server doesn't have memory_apply_patch, use legacy memory tool
|
||||
if (toolNames.includes("memory_apply_patch")) {
|
||||
try {
|
||||
const resp = await client.tools.list({ name: "memory_apply_patch" });
|
||||
const hasMemoryApplyPatch =
|
||||
Array.isArray(resp.items) && resp.items.length > 0;
|
||||
if (!hasMemoryApplyPatch) {
|
||||
console.warn(
|
||||
"memory_apply_patch tool not found on server; falling back to 'memory' tool",
|
||||
);
|
||||
toolNames = toolNames.map((n) =>
|
||||
n === "memory_apply_patch" ? "memory" : n,
|
||||
);
|
||||
}
|
||||
} catch (err) {
|
||||
// If the capability check fails for any reason, conservatively fall back to 'memory'
|
||||
console.warn(
|
||||
`Unable to verify memory_apply_patch availability (falling back to 'memory'): ${
|
||||
err instanceof Error ? err.message : String(err)
|
||||
}`,
|
||||
);
|
||||
toolNames = toolNames.map((n) =>
|
||||
n === "memory_apply_patch" ? "memory" : n,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Load memory blocks from .mdx files
|
||||
const defaultMemoryBlocks =
|
||||
@@ -312,6 +342,8 @@ export async function createAgent(
|
||||
enable_sleeptime: enableSleeptime,
|
||||
});
|
||||
|
||||
// Note: Preflight check above falls back to 'memory' when 'memory_apply_patch' is unavailable.
|
||||
|
||||
// Apply updateArgs if provided (e.g., reasoningEffort, verbosity, etc.)
|
||||
// Skip if updateArgs only contains context_window (already set in create)
|
||||
if (updateArgs && Object.keys(updateArgs).length > 0) {
|
||||
|
||||
@@ -3,4 +3,4 @@ The `loaded_skills` block has at least one skill loaded. You should:
|
||||
1. Check if loaded skills are relevant for the current task.
|
||||
2. For any skills that are irrelevant, unload them using the `memory` tool.
|
||||
If the block will be empty after unloading, add a "[CURRENTLY EMPTY]" tag.
|
||||
</system-reminder>
|
||||
</system-reminder>
|
||||
|
||||
@@ -149,6 +149,65 @@ export async function forceToolsetSwitch(
|
||||
// Remove old Letta tools and add new ones
|
||||
await unlinkToolsFromAgent(agentId);
|
||||
await linkToolsToAgent(agentId);
|
||||
|
||||
// Ensure base memory tool uses memory_apply_patch instead of legacy memory
|
||||
try {
|
||||
const client = await getClient();
|
||||
const agent = await client.agents.retrieve(agentId, {
|
||||
include: ["agent.tools"],
|
||||
});
|
||||
|
||||
const currentTools = agent.tools || [];
|
||||
const mapByName = new Map(currentTools.map((t) => [t.name, t.id]));
|
||||
|
||||
// Determine which memory tool we want based on toolset
|
||||
const desiredMemoryTool =
|
||||
toolsetName === "default" ? "memory" : "memory_apply_patch";
|
||||
const otherMemoryTool =
|
||||
desiredMemoryTool === "memory" ? "memory_apply_patch" : "memory";
|
||||
|
||||
// Ensure desired memory tool is attached
|
||||
let desiredId = mapByName.get(desiredMemoryTool);
|
||||
if (!desiredId) {
|
||||
const resp = await client.tools.list({ name: desiredMemoryTool });
|
||||
desiredId = resp.items[0]?.id;
|
||||
}
|
||||
if (!desiredId) {
|
||||
console.warn(
|
||||
`Could not find tool id for ${desiredMemoryTool}. Keeping existing memory tool if present.`,
|
||||
);
|
||||
}
|
||||
|
||||
const otherId = mapByName.get(otherMemoryTool);
|
||||
|
||||
// Build new tool_ids: add desired memory tool, remove the other if present
|
||||
const currentIds = currentTools
|
||||
.map((t) => t.id)
|
||||
.filter((id): id is string => typeof id === "string");
|
||||
const newIds = new Set(currentIds);
|
||||
|
||||
// Only swap if we have a valid desired tool id; otherwise keep existing
|
||||
if (desiredId) {
|
||||
if (otherId) newIds.delete(otherId);
|
||||
newIds.add(desiredId);
|
||||
}
|
||||
|
||||
// Update tool_rules: rewrite any rules targeting the other tool to the desired tool
|
||||
const updatedRules = (agent.tool_rules || []).map((r) =>
|
||||
r.tool_name === otherMemoryTool
|
||||
? { ...r, tool_name: desiredMemoryTool }
|
||||
: r,
|
||||
);
|
||||
|
||||
await client.agents.update(agentId, {
|
||||
tool_ids: Array.from(newIds),
|
||||
tool_rules: updatedRules,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Warning: Failed to enforce memory_apply_patch base tool: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -193,6 +252,62 @@ export async function switchToolsetForModel(
|
||||
await unlinkToolsFromAgent(agentId);
|
||||
await linkToolsToAgent(agentId);
|
||||
|
||||
// Ensure base memory tool uses memory_apply_patch instead of legacy memory
|
||||
try {
|
||||
const agentWithTools = await client.agents.retrieve(agentId, {
|
||||
include: ["agent.tools"],
|
||||
});
|
||||
const currentTools = agentWithTools.tools || [];
|
||||
const mapByName = new Map(currentTools.map((t) => [t.name, t.id]));
|
||||
|
||||
// Determine which memory tool we want based on provider
|
||||
const desiredMemoryTool = isOpenAIModel(resolvedModel)
|
||||
? "memory_apply_patch"
|
||||
: (await import("./manager")).isGeminiModel(resolvedModel)
|
||||
? "memory_apply_patch"
|
||||
: "memory";
|
||||
const otherMemoryTool =
|
||||
desiredMemoryTool === "memory" ? "memory_apply_patch" : "memory";
|
||||
|
||||
// Ensure desired memory tool attached
|
||||
let desiredId = mapByName.get(desiredMemoryTool);
|
||||
if (!desiredId) {
|
||||
const resp = await client.tools.list({ name: desiredMemoryTool });
|
||||
desiredId = resp.items[0]?.id;
|
||||
}
|
||||
if (!desiredId) {
|
||||
console.warn(
|
||||
`Could not find tool id for ${desiredMemoryTool}. Keeping existing memory tool if present.`,
|
||||
);
|
||||
}
|
||||
|
||||
const otherId = mapByName.get(otherMemoryTool);
|
||||
|
||||
const currentIds = currentTools
|
||||
.map((t) => t.id)
|
||||
.filter((id): id is string => typeof id === "string");
|
||||
const newIds = new Set(currentIds);
|
||||
if (desiredId) {
|
||||
if (otherId) newIds.delete(otherId);
|
||||
newIds.add(desiredId);
|
||||
}
|
||||
|
||||
const updatedRules = (agentWithTools.tool_rules || []).map((r) =>
|
||||
r.tool_name === otherMemoryTool
|
||||
? { ...r, tool_name: desiredMemoryTool }
|
||||
: r,
|
||||
);
|
||||
|
||||
await client.agents.update(agentId, {
|
||||
tool_ids: Array.from(newIds),
|
||||
tool_rules: updatedRules,
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
`Warning: Failed to enforce memory_apply_patch base tool: ${err instanceof Error ? err.message : String(err)}`,
|
||||
);
|
||||
}
|
||||
|
||||
const { isGeminiModel } = await import("./manager");
|
||||
const toolsetName = isOpenAIModel(resolvedModel)
|
||||
? "codex"
|
||||
|
||||
Reference in New Issue
Block a user