555e282cc4
pi-agent-plugin checks / lint (push) Has been cancelled
pi-agent-plugin checks / test (20) (push) Has been cancelled
pi-agent-plugin checks / test (22) (push) Has been cancelled
pi-agent-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / check_changes (push) Has been cancelled
TypeScript SDK CI / changelog_check (push) Has been cancelled
ci / changelog_check (push) Has been cancelled
ci / check_changes (push) Has been cancelled
ci / build_mem0 (3.10) (push) Has been cancelled
ci / build_mem0 (3.11) (push) Has been cancelled
ci / build_mem0 (3.12) (push) Has been cancelled
CLI Node CI / lint (push) Has been cancelled
CLI Node CI / test (20) (push) Has been cancelled
CLI Node CI / test (22) (push) Has been cancelled
CLI Node CI / build (push) Has been cancelled
CLI Python CI / lint (push) Has been cancelled
CLI Python CI / test (3.10) (push) Has been cancelled
CLI Python CI / test (3.11) (push) Has been cancelled
CLI Python CI / test (3.12) (push) Has been cancelled
CLI Python CI / build (push) Has been cancelled
openclaw checks / lint (push) Has been cancelled
openclaw checks / test (20) (push) Has been cancelled
openclaw checks / test (22) (push) Has been cancelled
openclaw checks / build (push) Has been cancelled
opencode-plugin checks / build (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / build_ts_sdk (22) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (20) (push) Has been cancelled
TypeScript SDK CI / integration_ts_sdk (22) (push) Has been cancelled
106 lines
5.9 KiB
TypeScript
106 lines
5.9 KiB
TypeScript
import { Type } from "@sinclair/typebox";
|
|
import type { AddOptions } from "../types.ts";
|
|
import { isSubagentSession } from "../isolation.ts";
|
|
import { isNoiseMessage, stripNoiseFromContent } from "../filtering.ts";
|
|
// v3.0.0: resolveCategories/ttlToExpirationDate removed - expiration_date/immutable no longer supported
|
|
import type { ToolDeps } from "./index.ts";
|
|
|
|
export function createMemoryAddTool(deps: ToolDeps) {
|
|
const { api, provider, resolveUserId, getCurrentSessionId, buildAddOptions, buildSearchOptions, skillsActive } = deps;
|
|
|
|
return {
|
|
name: "memory_add",
|
|
label: "Memory Add",
|
|
description: "Save important information to long-term memory via Mem0. Do this proactively, without being asked, whenever the user shares a preference, fact, decision, rule, or anything worth recalling in future sessions. Group related facts under one category.",
|
|
parameters: Type.Object({
|
|
text: Type.Optional(Type.String({ description: "Single fact to remember" })),
|
|
facts: Type.Optional(Type.Array(Type.String(), { description: "Array of facts to store. ALL must share the same category." })),
|
|
category: Type.Optional(Type.String({ description: 'Category: "identity", "preference", "decision", "rule", "project", "configuration", "technical", "relationship"' })),
|
|
importance: Type.Optional(Type.Number({ description: "Importance (0.0-1.0), omit for category default" })),
|
|
userId: Type.Optional(Type.String({ description: "User ID to scope this memory" })),
|
|
agentId: Type.Optional(Type.String({ description: "Agent ID namespace" })),
|
|
metadata: Type.Optional(Type.Record(Type.String(), Type.Unknown(), { description: "Additional metadata" })),
|
|
longTerm: Type.Optional(Type.Boolean({ description: "Long-term (default: true). Set false for session-scoped." })),
|
|
}),
|
|
|
|
async execute(_toolCallId: string, params: Record<string, unknown>) {
|
|
const p = params as {
|
|
text?: string; facts?: string[]; category?: string; importance?: number;
|
|
userId?: string; agentId?: string; metadata?: Record<string, unknown>; longTerm?: boolean;
|
|
};
|
|
|
|
const rawFacts: string[] = p.facts?.length ? p.facts : (p.text ? [p.text] : []);
|
|
if (rawFacts.length === 0) {
|
|
return { content: [{ type: "text", text: "No facts provided. Pass 'text' or 'facts' array." }], details: { error: "missing_facts" } };
|
|
}
|
|
|
|
// Filter out noise and clean the facts before storing
|
|
const allFacts = rawFacts
|
|
.map((f) => stripNoiseFromContent(f))
|
|
.filter((f) => f.length > 0 && !isNoiseMessage(f));
|
|
|
|
if (allFacts.length === 0) {
|
|
return { content: [{ type: "text", text: "All provided facts were filtered as noise. Nothing stored." }], details: { error: "all_noise" } };
|
|
}
|
|
|
|
const start = Date.now();
|
|
try {
|
|
const currentSessionId = getCurrentSessionId();
|
|
|
|
if (isSubagentSession(currentSessionId)) {
|
|
return { content: [{ type: "text", text: "Memory storage is not available in subagent sessions." }], details: { error: "subagent_blocked" } };
|
|
}
|
|
|
|
const uid = resolveUserId({ agentId: p.agentId, userId: p.userId });
|
|
const runId = !(p.longTerm ?? true) && currentSessionId ? currentSessionId : undefined;
|
|
|
|
if (skillsActive) {
|
|
const rawMetadata = p.metadata;
|
|
const category = p.category ?? rawMetadata?.category as string | undefined;
|
|
const importance = p.importance ?? rawMetadata?.importance as number | undefined;
|
|
const parsedMetadata: Record<string, unknown> = {
|
|
...(rawMetadata ?? {}),
|
|
...(category && { category }),
|
|
...(importance !== undefined && { importance }),
|
|
};
|
|
|
|
const addOpts: AddOptions = {
|
|
user_id: uid, source: "OPENCLAW", infer: false,
|
|
deduced_memories: allFacts, metadata: parsedMetadata ?? {},
|
|
};
|
|
if (runId) addOpts.run_id = runId;
|
|
|
|
const result = await provider.add([{ role: "user", content: allFacts.join("\n") }], addOpts);
|
|
const count = result.results?.length ?? 0;
|
|
api.logger.info(`openclaw-mem0: stored ${count} memor${count === 1 ? "y" : "ies"} (infer=false, category=${category ?? "none"})`);
|
|
|
|
deps.captureToolEvent("memory_add", { success: true, latency_ms: Date.now() - start, fact_count: allFacts.length, mode: "skills" });
|
|
return {
|
|
content: [{ type: "text", text: `Stored ${allFacts.length} fact(s) [${category ?? "uncategorized"}]: ${allFacts.map(f => `"${f.slice(0, 60)}${f.length > 60 ? "..." : ""}"`).join(", ")}` }],
|
|
details: { action: "stored", mode: "skills", category, factCount: allFacts.length, results: result.results },
|
|
};
|
|
}
|
|
|
|
const combinedText = allFacts.join("\n");
|
|
|
|
const result = await provider.add([{ role: "user", content: combinedText }], buildAddOptions(uid, runId, currentSessionId));
|
|
const added = result.results?.filter((r) => r.event === "ADD") ?? [];
|
|
const updated = result.results?.filter((r) => r.event === "UPDATE") ?? [];
|
|
const summary = [];
|
|
if (added.length > 0) summary.push(`${added.length} added`);
|
|
if (updated.length > 0) summary.push(`${updated.length} updated`);
|
|
if (summary.length === 0) summary.push("No new memories extracted");
|
|
|
|
deps.captureToolEvent("memory_add", { success: true, latency_ms: Date.now() - start, fact_count: allFacts.length });
|
|
return {
|
|
content: [{ type: "text", text: `Stored: ${summary.join(", ")}. ${result.results?.map((r) => `[${r.event}] ${r.memory}`).join("; ") ?? ""}` }],
|
|
details: { action: "stored", results: result.results },
|
|
};
|
|
} catch (err) {
|
|
deps.captureToolEvent("memory_add", { success: false, latency_ms: Date.now() - start, error: String(err) });
|
|
return { content: [{ type: "text", text: `Memory add failed: ${String(err)}` }], details: { error: String(err) } };
|
|
}
|
|
},
|
|
};
|
|
}
|