148 lines
4.8 KiB
JavaScript
148 lines
4.8 KiB
JavaScript
/**
|
|
* LLMemory Plugin for OpenCode
|
|
*
|
|
* Provides a persistent memory/journal system for AI agents.
|
|
* Memories are stored in SQLite and searchable across sessions.
|
|
*/
|
|
import { tool } from "@opencode-ai/plugin";
|
|
import { spawn } from "child_process";
|
|
import { fileURLToPath } from 'url';
|
|
import { dirname, join } from 'path';
|
|
|
|
const __filename = fileURLToPath(import.meta.url);
|
|
const __dirname = dirname(__filename);
|
|
const MEMORY_CLI = join(__dirname, '../llmemory/bin/llmemory');
|
|
|
|
function runMemoryCommand(args) {
|
|
return new Promise((resolve, reject) => {
|
|
const child = spawn('node', [MEMORY_CLI, ...args], {
|
|
env: { ...process.env }
|
|
});
|
|
|
|
let stdout = '';
|
|
let stderr = '';
|
|
|
|
child.stdout.on('data', (data) => {
|
|
stdout += data.toString();
|
|
});
|
|
|
|
child.stderr.on('data', (data) => {
|
|
stderr += data.toString();
|
|
});
|
|
|
|
child.on('close', (code) => {
|
|
if (code !== 0) {
|
|
reject(new Error(stderr || `Command failed with code ${code}`));
|
|
} else {
|
|
resolve(stdout);
|
|
}
|
|
});
|
|
});
|
|
}
|
|
|
|
export const LLMemoryPlugin = async (ctx) => {
|
|
const tools = {
|
|
memory_store: tool({
|
|
description: `Store a memory for future reference. Use this to remember important information across sessions.
|
|
|
|
Examples:
|
|
- Store implementation decisions: "Decided to use JWT for auth instead of sessions"
|
|
- Record completed work: "Implemented user authentication with email/password"
|
|
- Save debugging insights: "Bug was caused by race condition in async handler"
|
|
- Document project context: "Client prefers Material-UI over Tailwind"
|
|
|
|
Memories are searchable by content and tags.`,
|
|
args: {
|
|
content: tool.schema.string()
|
|
.describe("The memory content to store (required)"),
|
|
tags: tool.schema.string()
|
|
.optional()
|
|
.describe("Comma-separated tags for categorization (e.g., 'backend,auth,security')"),
|
|
expires: tool.schema.string()
|
|
.optional()
|
|
.describe("Optional expiration date (ISO format, e.g., '2026-12-31')"),
|
|
by: tool.schema.string()
|
|
.optional()
|
|
.describe("Agent/user identifier (defaults to 'agent')")
|
|
},
|
|
async execute(args) {
|
|
const cmdArgs = ['store', args.content];
|
|
if (args.tags) cmdArgs.push('--tags', args.tags);
|
|
if (args.expires) cmdArgs.push('--expires', args.expires);
|
|
if (args.by) cmdArgs.push('--by', args.by);
|
|
|
|
try {
|
|
const result = await runMemoryCommand(cmdArgs);
|
|
return result;
|
|
} catch (error) {
|
|
return `Error storing memory: ${error.message}`;
|
|
}
|
|
}
|
|
}),
|
|
|
|
memory_search: tool({
|
|
description: `Search stored memories by content and/or tags. Returns relevant memories from past sessions.
|
|
|
|
Use cases:
|
|
- Find past decisions: "authentication"
|
|
- Recall debugging insights: "race condition"
|
|
- Look up project context: "client preferences"
|
|
- Review completed work: "implemented"
|
|
|
|
Supports filtering by tags, date ranges, and limiting results.`,
|
|
args: {
|
|
query: tool.schema.string()
|
|
.describe("Search query (case-insensitive substring match)"),
|
|
tags: tool.schema.string()
|
|
.optional()
|
|
.describe("Filter by tags (AND logic, comma-separated)"),
|
|
any_tag: tool.schema.string()
|
|
.optional()
|
|
.describe("Filter by tags (OR logic, comma-separated)"),
|
|
limit: tool.schema.number()
|
|
.optional()
|
|
.describe("Maximum results to return (default: 10)")
|
|
},
|
|
async execute(args) {
|
|
const cmdArgs = ['search', args.query, '--json'];
|
|
if (args.tags) cmdArgs.push('--tags', args.tags);
|
|
if (args.any_tag) cmdArgs.push('--any-tag', args.any_tag);
|
|
if (args.limit) cmdArgs.push('--limit', String(args.limit));
|
|
|
|
try {
|
|
const result = await runMemoryCommand(cmdArgs);
|
|
return result;
|
|
} catch (error) {
|
|
return `Error searching memories: ${error.message}`;
|
|
}
|
|
}
|
|
}),
|
|
|
|
memory_list: tool({
|
|
description: `List recent memories, optionally filtered by tags. Useful for reviewing recent work or exploring stored context.`,
|
|
args: {
|
|
limit: tool.schema.number()
|
|
.optional()
|
|
.describe("Maximum results to return (default: 20)"),
|
|
tags: tool.schema.string()
|
|
.optional()
|
|
.describe("Filter by tags (comma-separated)")
|
|
},
|
|
async execute(args) {
|
|
const cmdArgs = ['list', '--json'];
|
|
if (args.limit) cmdArgs.push('--limit', String(args.limit));
|
|
if (args.tags) cmdArgs.push('--tags', args.tags);
|
|
|
|
try {
|
|
const result = await runMemoryCommand(cmdArgs);
|
|
return result;
|
|
} catch (error) {
|
|
return `Error listing memories: ${error.message}`;
|
|
}
|
|
}
|
|
})
|
|
};
|
|
|
|
return { tool: tools };
|
|
};
|