import { Type } from "@sinclair/typebox"; import { listMemoryCorpusSupplements, resolveMemorySearchConfig, resolveSessionAgentId, type MemoryCorpusGetResult, type MemoryCorpusSearchResult, type AnyAgentTool, type OpenClawConfig, } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/text-runtime"; type MemoryToolRuntime = typeof import("./tools.runtime.js"); type MemorySearchManagerResult = Awaited< ReturnType<(typeof import("./memory/index.js"))["getMemorySearchManager"]> >; let memoryToolRuntimePromise: Promise | null = null; export async function loadMemoryToolRuntime(): Promise { memoryToolRuntimePromise ??= import("./tools.runtime.js"); return await memoryToolRuntimePromise; } export const MemorySearchSchema = Type.Object({ query: Type.String(), maxResults: Type.Optional(Type.Number()), minScore: Type.Optional(Type.Number()), corpus: Type.Optional( Type.Union([Type.Literal("memory"), Type.Literal("wiki"), Type.Literal("all")]), ), }); export const MemoryGetSchema = Type.Object({ path: Type.String(), from: Type.Optional(Type.Number()), lines: Type.Optional(Type.Number()), corpus: Type.Optional( Type.Union([Type.Literal("memory"), Type.Literal("wiki"), Type.Literal("all")]), ), }); export function resolveMemoryToolContext(options: { config?: OpenClawConfig; agentSessionKey?: string; }) { const cfg = options.config; if (!cfg) { return null; } const agentId = resolveSessionAgentId({ sessionKey: options.agentSessionKey, config: cfg, }); if (!resolveMemorySearchConfig(cfg, agentId)) { return null; } return { cfg, agentId }; } export async function getMemoryManagerContext(params: { cfg: OpenClawConfig; agentId: string; }): Promise< | { manager: NonNullable; } | { error: string | undefined; } > { return await getMemoryManagerContextWithPurpose({ ...params, purpose: undefined }); } export async function getMemoryManagerContextWithPurpose(params: { cfg: OpenClawConfig; agentId: string; purpose?: "default" | "status"; }): Promise< | { manager: NonNullable; } | { error: string | undefined; } > { const { getMemorySearchManager } = await loadMemoryToolRuntime(); const { manager, error } = await getMemorySearchManager({ cfg: params.cfg, agentId: params.agentId, purpose: params.purpose, }); return manager ? { manager } : { error }; } export function createMemoryTool(params: { options: { config?: OpenClawConfig; agentSessionKey?: string; }; label: string; name: string; description: string; parameters: typeof MemorySearchSchema | typeof MemoryGetSchema; execute: (ctx: { cfg: OpenClawConfig; agentId: string }) => AnyAgentTool["execute"]; }): AnyAgentTool | null { const ctx = resolveMemoryToolContext(params.options); if (!ctx) { return null; } return { label: params.label, name: params.name, description: params.description, parameters: params.parameters, execute: params.execute(ctx), }; } export function buildMemorySearchUnavailableResult(error: string | undefined) { const reason = (error ?? "memory search unavailable").trim() || "memory search unavailable"; const isQuotaError = /insufficient_quota|quota|429/.test(normalizeLowercaseStringOrEmpty(reason)); const warning = isQuotaError ? "Memory search is unavailable because the embedding provider quota is exhausted." : "Memory search is unavailable due to an embedding/provider error."; const action = isQuotaError ? "Top up or switch embedding provider, then retry memory_search." : "Check embedding provider configuration and retry memory_search."; return { results: [], disabled: true, unavailable: true, error: reason, warning, action, }; } export async function searchMemoryCorpusSupplements(params: { query: string; maxResults?: number; agentSessionKey?: string; corpus?: "memory" | "wiki" | "all"; }): Promise { if (params.corpus === "memory") { return []; } const supplements = listMemoryCorpusSupplements(); if (supplements.length === 0) { return []; } const results = ( await Promise.all( supplements.map(async (registration) => await registration.supplement.search(params)), ) ).flat(); return results .toSorted((left, right) => { if (left.score !== right.score) { return right.score - left.score; } return left.path.localeCompare(right.path); }) .slice(0, Math.max(1, params.maxResults ?? 10)); } export async function getMemoryCorpusSupplementResult(params: { lookup: string; fromLine?: number; lineCount?: number; agentSessionKey?: string; corpus?: "memory" | "wiki" | "all"; }): Promise { if (params.corpus === "memory") { return null; } for (const registration of listMemoryCorpusSupplements()) { const result = await registration.supplement.get(params); if (result) { return result; } } return null; }