@ghilteras/opencode-agent-memory 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/tools.ts ADDED
@@ -0,0 +1,200 @@
1
+ import { tool } from "@opencode-ai/plugin";
2
+
3
+ import type { JournalStore } from "./journal";
4
+ import type { MemoryScope, MemoryStore } from "./memory";
5
+
6
+ export function MemoryList(store: MemoryStore) {
7
+ return tool({
8
+ description: "List available memory blocks (labels, descriptions, sizes).",
9
+ args: {
10
+ scope: tool.schema.enum(["all", "global", "project"]).optional(),
11
+ },
12
+ async execute(args) {
13
+ // Default to "all" for list (show everything)
14
+ const scope = (args.scope ?? "all") as MemoryScope | "all";
15
+ const blocks = await store.listBlocks(scope);
16
+ if (blocks.length === 0) {
17
+ return "No memory blocks found.";
18
+ }
19
+
20
+ return blocks
21
+ .map(
22
+ (b) =>
23
+ `${b.scope}:${b.label}\n read_only=${b.readOnly} chars=${b.value.length}/${b.limit}\n ${b.description}`,
24
+ )
25
+ .join("\n\n");
26
+ },
27
+ });
28
+ }
29
+
30
+ export function MemorySet(store: MemoryStore) {
31
+ return tool({
32
+ description: "Create or update a memory block (full overwrite).",
33
+ args: {
34
+ label: tool.schema.string(),
35
+ scope: tool.schema.enum(["global", "project"]).optional(),
36
+ value: tool.schema.string(),
37
+ description: tool.schema.string().optional(),
38
+ limit: tool.schema.number().int().positive().optional(),
39
+ },
40
+ async execute(args) {
41
+ // Default to "project" for mutations (safer default)
42
+ const scope = (args.scope ?? "project") as MemoryScope;
43
+ const ts = await store.setBlock(scope, args.label, args.value, {
44
+ description: args.description,
45
+ limit: args.limit,
46
+ });
47
+ return `Updated memory block ${scope}:${args.label} at ${ts.toISOString()}.`;
48
+ },
49
+ });
50
+ }
51
+
52
+ export function MemoryReplace(store: MemoryStore) {
53
+ return tool({
54
+ description: "Replace a substring within a memory block.",
55
+ args: {
56
+ label: tool.schema.string(),
57
+ scope: tool.schema.enum(["global", "project"]).optional(),
58
+ oldText: tool.schema.string(),
59
+ newText: tool.schema.string(),
60
+ },
61
+ async execute(args) {
62
+ // Default to "project" for mutations (safer default)
63
+ const scope = (args.scope ?? "project") as MemoryScope;
64
+ const ts = await store.replaceInBlock(scope, args.label, args.oldText, args.newText);
65
+ return `Updated memory block ${scope}:${args.label} at ${ts.toISOString()}.`;
66
+ },
67
+ });
68
+ }
69
+
70
+ export type JournalContext = {
71
+ directory: string;
72
+ model: string;
73
+ provider: string;
74
+ };
75
+
76
+ export function JournalWrite(
77
+ store: JournalStore,
78
+ ctx: JournalContext,
79
+ ) {
80
+ return tool({
81
+ description:
82
+ "Write a new journal entry. Use this to capture insights, technical discoveries, " +
83
+ "design decisions, observations, or reflections. Entries are append-only and cannot be edited. " +
84
+ "Tags are optional comma-separated names, e.g. \"perf, debugging\".",
85
+ args: {
86
+ title: tool.schema.string(),
87
+ body: tool.schema.string(),
88
+ tags: tool.schema.string().optional(),
89
+ },
90
+ async execute(args, toolCtx) {
91
+ const tags = args.tags
92
+ ? args.tags
93
+ .split(",")
94
+ .map((t: string) => t.trim())
95
+ .filter(Boolean)
96
+ : undefined;
97
+
98
+ const entry = await store.write({
99
+ title: args.title,
100
+ body: args.body,
101
+ project: ctx.directory,
102
+ model: ctx.model,
103
+ provider: ctx.provider,
104
+ agent: toolCtx.agent,
105
+ sessionId: toolCtx.sessionID,
106
+ tags,
107
+ });
108
+
109
+ return `Journal entry created: ${entry.id}\n title: ${entry.title}\n created: ${entry.created.toISOString()}`;
110
+ },
111
+ });
112
+ }
113
+
114
+ export function JournalRead(store: JournalStore) {
115
+ return tool({
116
+ description:
117
+ "Read a specific journal entry by its ID. Returns the full entry " +
118
+ "including metadata and body.",
119
+ args: {
120
+ id: tool.schema.string(),
121
+ },
122
+ async execute(args) {
123
+ const entry = await store.read(args.id);
124
+
125
+ const meta = [
126
+ `title: ${entry.title}`,
127
+ `created: ${entry.created.toISOString()}`,
128
+ entry.project ? `project: ${entry.project}` : null,
129
+ entry.model ? `model: ${entry.model}` : null,
130
+ entry.provider ? `provider: ${entry.provider}` : null,
131
+ entry.agent ? `agent: ${entry.agent}` : null,
132
+ entry.sessionId ? `session: ${entry.sessionId}` : null,
133
+ entry.tags.length > 0
134
+ ? `tags: ${entry.tags.join(", ")}`
135
+ : null,
136
+ ]
137
+ .filter(Boolean)
138
+ .join("\n");
139
+
140
+ return `${meta}\n\n${entry.body}`;
141
+ },
142
+ });
143
+ }
144
+
145
+ export function JournalSearch(store: JournalStore) {
146
+ return tool({
147
+ description:
148
+ "Search journal entries using semantic similarity. Returns matching entries " +
149
+ "sorted by relevance. All filters are optional and combined with AND logic. " +
150
+ "Use with no arguments to list recent entries. Use offset to paginate.",
151
+ args: {
152
+ text: tool.schema.string().optional(),
153
+ project: tool.schema.string().optional(),
154
+ tags: tool.schema.string().optional(),
155
+ limit: tool.schema.number().int().positive().optional(),
156
+ offset: tool.schema.number().int().nonnegative().optional(),
157
+ },
158
+ async execute(args) {
159
+ const tags = args.tags
160
+ ? args.tags
161
+ .split(",")
162
+ .map((t: string) => t.trim())
163
+ .filter(Boolean)
164
+ : undefined;
165
+
166
+ const result = await store.search({
167
+ text: args.text,
168
+ project: args.project,
169
+ tags,
170
+ limit: args.limit,
171
+ offset: args.offset,
172
+ });
173
+
174
+ if (result.entries.length === 0) {
175
+ const tagsLine =
176
+ result.allTags.length > 0
177
+ ? `\nTags in use: ${result.allTags.join(", ")}`
178
+ : "";
179
+ return `No journal entries found.${tagsLine}`;
180
+ }
181
+
182
+ const offset = args.offset ?? 0;
183
+ const header = `Found ${result.total} entries (showing ${offset + 1}–${offset + result.entries.length}):`;
184
+ const tagsLine =
185
+ result.allTags.length > 0
186
+ ? `\nTags in use: ${result.allTags.join(", ")}`
187
+ : "";
188
+
189
+ const lines = result.entries.map((e) => {
190
+ const tagStr =
191
+ e.tags.length > 0
192
+ ? ` [${e.tags.join(", ")}]`
193
+ : "";
194
+ return `${e.id}\n ${e.title}${tagStr}\n ${e.created.toISOString()}`;
195
+ });
196
+
197
+ return `${header}${tagsLine}\n\n${lines.join("\n\n")}`;
198
+ },
199
+ });
200
+ }