@openagentsy/memory 0.0.0-stage → 0.2.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.
Files changed (47) hide show
  1. package/LICENSE +674 -0
  2. package/README.md +33 -2
  3. package/dist/acceptance.test.d.ts +2 -0
  4. package/dist/acceptance.test.d.ts.map +1 -0
  5. package/dist/acceptance.test.js +138 -0
  6. package/dist/acceptance.test.js.map +1 -0
  7. package/dist/fixtures.d.ts +30 -0
  8. package/dist/fixtures.d.ts.map +1 -0
  9. package/dist/fixtures.js +49 -0
  10. package/dist/fixtures.js.map +1 -0
  11. package/dist/inbox.d.ts +131 -0
  12. package/dist/inbox.d.ts.map +1 -0
  13. package/dist/inbox.js +161 -0
  14. package/dist/inbox.js.map +1 -0
  15. package/dist/inbox.test.d.ts +2 -0
  16. package/dist/inbox.test.d.ts.map +1 -0
  17. package/dist/inbox.test.js +144 -0
  18. package/dist/inbox.test.js.map +1 -0
  19. package/dist/index.d.ts +15 -0
  20. package/dist/index.d.ts.map +1 -0
  21. package/dist/index.js +15 -0
  22. package/dist/index.js.map +1 -0
  23. package/dist/model.d.ts +85 -0
  24. package/dist/model.d.ts.map +1 -0
  25. package/dist/model.js +71 -0
  26. package/dist/model.js.map +1 -0
  27. package/dist/permissions.test.d.ts +2 -0
  28. package/dist/permissions.test.d.ts.map +1 -0
  29. package/dist/permissions.test.js +53 -0
  30. package/dist/permissions.test.js.map +1 -0
  31. package/dist/store.d.ts +142 -0
  32. package/dist/store.d.ts.map +1 -0
  33. package/dist/store.js +263 -0
  34. package/dist/store.js.map +1 -0
  35. package/dist/store.test.d.ts +2 -0
  36. package/dist/store.test.d.ts.map +1 -0
  37. package/dist/store.test.js +223 -0
  38. package/dist/store.test.js.map +1 -0
  39. package/dist/tools.d.ts +86 -0
  40. package/dist/tools.d.ts.map +1 -0
  41. package/dist/tools.js +108 -0
  42. package/dist/tools.js.map +1 -0
  43. package/dist/tools.test.d.ts +2 -0
  44. package/dist/tools.test.d.ts.map +1 -0
  45. package/dist/tools.test.js +142 -0
  46. package/dist/tools.test.js.map +1 -0
  47. package/package.json +47 -4
package/dist/store.js ADDED
@@ -0,0 +1,263 @@
1
+ import { appendFile, mkdir, readFile, stat } from 'node:fs/promises';
2
+ import { dirname, join } from 'node:path';
3
+ import { etagOf, redact } from '@openagentsy/core';
4
+ import { createMemoryId, isMemoryEntry, isMemoryMarker, isValidCategory, MAX_TEXT_LENGTH, normalizeText, } from './model.js';
5
+ /**
6
+ * The store of what a person decided to keep.
7
+ *
8
+ * Append-only: an entry is written once and never changed. A person who
9
+ * forgets or verifies a memory appends a marker instead, and the view this
10
+ * class returns folds the markers into the entries. The file therefore only
11
+ * ever grows, and a line in it is never rewritten.
12
+ */
13
+ export class MemoryStore {
14
+ root;
15
+ now;
16
+ onAudit;
17
+ cached;
18
+ constructor(options) {
19
+ this.root = options.root;
20
+ this.now = options.now ?? (() => new Date());
21
+ this.onAudit = options.onAudit;
22
+ }
23
+ /** The file the store lives in. */
24
+ get path() {
25
+ return join(this.root, '.openagentsy', 'memory.jsonl');
26
+ }
27
+ /**
28
+ * Add a memory.
29
+ *
30
+ * The redaction runs first: nothing reaches the file that carries a
31
+ * secret, and the count comes back with the outcome so the caller can
32
+ * report it. A text that is already a duplicate is refused with the id of
33
+ * the entry that holds it, because a memory that is already known is not
34
+ * a new thing.
35
+ */
36
+ async append(input) {
37
+ const redacted = redact(input.text);
38
+ const redactions = redacted.count;
39
+ const text = redacted.text.trim();
40
+ if (text.length === 0) {
41
+ return { ok: false, reason: 'invalid', detail: 'the text is empty', redactions };
42
+ }
43
+ if (text.length > MAX_TEXT_LENGTH) {
44
+ return {
45
+ ok: false,
46
+ reason: 'invalid',
47
+ detail: `the text is longer than ${MAX_TEXT_LENGTH} characters`,
48
+ redactions,
49
+ };
50
+ }
51
+ if (!isValidCategory(input.category)) {
52
+ return {
53
+ ok: false,
54
+ reason: 'invalid',
55
+ detail: 'the category is not a tag: a lowercase word, words joined by single hyphens',
56
+ redactions,
57
+ };
58
+ }
59
+ const { entries, tags } = await this.read();
60
+ const tag = etagOf(normalizeText(text));
61
+ for (const entry of entries) {
62
+ if (tags.get(entry.id) === tag) {
63
+ return { ok: false, reason: 'duplicate', id: entry.id, redactions };
64
+ }
65
+ }
66
+ const entry = {
67
+ id: createMemoryId(),
68
+ text,
69
+ category: input.category,
70
+ source: input.source,
71
+ confidence: input.confidence,
72
+ created_at: this.now().toISOString(),
73
+ // A memory is trusted on write (the human's decision of 2026-10-28):
74
+ // the archivist's judgement is the source, and a person marks a
75
+ // memory unverified when they disagree. The flag is what the search
76
+ // reads before anyone has said anything about the memory.
77
+ verified: input.verified ?? true,
78
+ };
79
+ await this.appendLine(entry);
80
+ return { ok: true, entry, redactions };
81
+ }
82
+ /** Every memory that is neither forgotten nor superseded. */
83
+ async entries() {
84
+ const { entries, markers } = await this.read();
85
+ const forgotten = new Set();
86
+ const verified = new Set();
87
+ for (const marker of markers) {
88
+ if (marker.kind === 'forget')
89
+ forgotten.add(marker.id);
90
+ else
91
+ verified.add(marker.id);
92
+ }
93
+ return entries
94
+ .filter((entry) => !forgotten.has(entry.id))
95
+ .map((entry) => (verified.has(entry.id) ? { ...entry, verified: true } : entry));
96
+ }
97
+ /**
98
+ * Find the memories that mention the terms of a query.
99
+ *
100
+ * The store is small - a target of under ten thousand entries - so the
101
+ * scan is over the parsed lines, and the parsed lines are cached against
102
+ * the file's stat, so a search that follows a search reads the file once.
103
+ */
104
+ async search(query) {
105
+ const entries = await this.entries();
106
+ const terms = termsOf(query.q);
107
+ const category = query.category?.toLowerCase();
108
+ const scored = entries
109
+ .filter((entry) => inCategory(entry, category))
110
+ .map((entry) => ({ entry, score: scoreOf(entry, terms) }))
111
+ .filter((result) => result.score > 0);
112
+ const ordering = query.ordering ?? 'lost-in-the-middle';
113
+ scored.sort(ordering === 'recency' ? byRecency : byRelevance);
114
+ const top = query.limit === undefined ? scored : scored.slice(0, query.limit);
115
+ return ordering === 'lost-in-the-middle' ? lostInTheMiddle(top) : top;
116
+ }
117
+ /**
118
+ * Forget a memory.
119
+ *
120
+ * The entry stays in the file and the store stops returning it. The
121
+ * removal command is a later phase; this is the path it will take, and
122
+ * the person who did it is on the marker.
123
+ */
124
+ async forget(id, by = 'unknown') {
125
+ const { entries } = await this.read();
126
+ if (!entries.some((entry) => entry.id === id))
127
+ return false;
128
+ await this.appendLine({ kind: 'forget', id, by, at: this.now().toISOString() });
129
+ await this.onAudit?.('oa.memory.forget', { id, decidedBy: by });
130
+ return true;
131
+ }
132
+ /**
133
+ * Mark a memory verified.
134
+ *
135
+ * A memory is trusted on write, and this is the other direction: a
136
+ * person says it is true. The archivist's label is not a verification:
137
+ * the archivist never looked at the memory.
138
+ */
139
+ async verify(id, decidedBy) {
140
+ const { entries } = await this.read();
141
+ if (!entries.some((entry) => entry.id === id))
142
+ return false;
143
+ await this.appendLine({ kind: 'verify', id, by: decidedBy, at: this.now().toISOString() });
144
+ await this.onAudit?.('oa.memory.verify', { id, decidedBy });
145
+ return true;
146
+ }
147
+ /** Append one line to the file. */
148
+ async appendLine(line) {
149
+ const path = this.path;
150
+ await mkdir(dirname(path), { recursive: true });
151
+ await appendFile(path, `${JSON.stringify(line)}\n`, 'utf8');
152
+ }
153
+ /**
154
+ * The parsed file, from the cache when the file has not changed.
155
+ *
156
+ * The key is the file's size and modification time, so a file that was
157
+ * appended to misses the cache and a file that was not is read once.
158
+ */
159
+ async read() {
160
+ const path = this.path;
161
+ let key;
162
+ try {
163
+ const info = await stat(path);
164
+ key = `${info.mtimeMs}:${info.size}`;
165
+ }
166
+ catch {
167
+ return { entries: [], markers: [], tags: new Map() };
168
+ }
169
+ if (this.cached?.key === key)
170
+ return this.cached.file;
171
+ const text = await readFile(path, 'utf8');
172
+ const entries = [];
173
+ const markers = [];
174
+ const tags = new Map();
175
+ for (const line of text.split('\n')) {
176
+ const trimmed = line.trim();
177
+ if (trimmed.length === 0)
178
+ continue;
179
+ let parsed;
180
+ try {
181
+ parsed = JSON.parse(trimmed);
182
+ }
183
+ catch {
184
+ continue;
185
+ }
186
+ if (isMemoryEntry(parsed)) {
187
+ entries.push(parsed);
188
+ tags.set(parsed.id, etagOf(normalizeText(parsed.text)));
189
+ }
190
+ else if (isMemoryMarker(parsed)) {
191
+ markers.push(parsed);
192
+ }
193
+ }
194
+ const file = { entries, markers, tags };
195
+ this.cached = { key, file };
196
+ return file;
197
+ }
198
+ }
199
+ /** The terms of a query, lowercased. */
200
+ function termsOf(query) {
201
+ return query
202
+ .toLowerCase()
203
+ .split(/\s+/)
204
+ .filter((term) => term.length > 0);
205
+ }
206
+ /** Whether an entry is in the category the query names. */
207
+ function inCategory(entry, category) {
208
+ return category === undefined || entry.category.toLowerCase() === category;
209
+ }
210
+ /**
211
+ * The score of one entry against the terms of a query.
212
+ *
213
+ * A term in the text is worth more than a term in the category: the text is
214
+ * what the memory says, and the category only says what it is about.
215
+ */
216
+ function scoreOf(entry, terms) {
217
+ const text = entry.text.toLowerCase();
218
+ const category = entry.category.toLowerCase();
219
+ let score = 0;
220
+ for (const term of terms) {
221
+ if (text.includes(term))
222
+ score += 2;
223
+ if (category.includes(term))
224
+ score += 1;
225
+ }
226
+ return score;
227
+ }
228
+ /** The best match first, and the newest of two equals first. */
229
+ function byRelevance(a, b) {
230
+ if (b.score !== a.score)
231
+ return b.score - a.score;
232
+ if (a.entry.created_at !== b.entry.created_at) {
233
+ return a.entry.created_at < b.entry.created_at ? 1 : -1;
234
+ }
235
+ return a.entry.id.localeCompare(b.entry.id);
236
+ }
237
+ /** The newest first. */
238
+ function byRecency(a, b) {
239
+ if (a.entry.created_at !== b.entry.created_at) {
240
+ return a.entry.created_at < b.entry.created_at ? 1 : -1;
241
+ }
242
+ return a.entry.id.localeCompare(b.entry.id);
243
+ }
244
+ /**
245
+ * The order that keeps the best results where a model looks first.
246
+ *
247
+ * A model attends to the start and the end of a context more than to the
248
+ * middle, so the strongest result goes first and the second strongest goes
249
+ * last, and the rest fill in between. The ranking is unchanged: only the
250
+ * positions move.
251
+ */
252
+ export function lostInTheMiddle(ranked) {
253
+ const front = [];
254
+ const back = [];
255
+ ranked.forEach((item, index) => {
256
+ if (index % 2 === 0)
257
+ front.push(item);
258
+ else
259
+ back.push(item);
260
+ });
261
+ return [...front, ...back.reverse()];
262
+ }
263
+ //# sourceMappingURL=store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.js","sourceRoot":"","sources":["../src/store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=store.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.d.ts","sourceRoot":"","sources":["../src/store.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,223 @@
1
+ import { existsSync } from 'node:fs';
2
+ import { readFile } from 'node:fs/promises';
3
+ import { join } from 'node:path';
4
+ import { describe, expect, it } from 'vitest';
5
+ import { temporaryRoot } from './fixtures.js';
6
+ import { MemoryStore } from './store.js';
7
+ /** A key that looks like an AWS access key, so the redaction finds it. */
8
+ const SEEDED_KEY = 'AKIAIOSFODNN7EXAMPLE';
9
+ async function storeIn(root, onAudit) {
10
+ return new MemoryStore({
11
+ root,
12
+ now: () => new Date('2026-10-26T12:00:00.000Z'),
13
+ ...(onAudit === undefined ? {} : { onAudit }),
14
+ });
15
+ }
16
+ describe('the memory store', () => {
17
+ it('writes one line per memory', async () => {
18
+ const { root, remove } = await temporaryRoot();
19
+ try {
20
+ const store = await storeIn(root);
21
+ const outcome = await store.append({
22
+ text: 'The lease guard refuses a write to a file no lease covers.',
23
+ category: 'build',
24
+ source: '7 · 2a-1',
25
+ confidence: 'inferred',
26
+ });
27
+ expect(outcome.ok).toBe(true);
28
+ const path = join(root, '.openagentsy', 'memory.jsonl');
29
+ const lines = (await readFile(path, 'utf8')).trim().split('\n');
30
+ expect(lines).toHaveLength(1);
31
+ const entry = JSON.parse(lines[0] ?? '{}');
32
+ expect(entry.id).toMatch(/^oa-mem-[A-Za-z0-9_-]{6}$/);
33
+ expect(entry.category).toBe('build');
34
+ expect(entry.source).toBe('7 · 2a-1');
35
+ expect(entry.confidence).toBe('inferred');
36
+ expect(entry.created_at).toBe('2026-10-26T12:00:00.000Z');
37
+ // A memory is trusted on write; a person marks it unverified when
38
+ // they disagree (the human's decision of 2026-10-28).
39
+ expect(entry.verified).toBe(true);
40
+ }
41
+ finally {
42
+ await remove();
43
+ }
44
+ });
45
+ it('redacts before it writes, and says how much it redacted', async () => {
46
+ const { root, remove } = await temporaryRoot();
47
+ try {
48
+ const store = await storeIn(root);
49
+ const outcome = await store.append({
50
+ text: `The key is ${SEEDED_KEY} and it must not be kept.`,
51
+ category: 'build',
52
+ source: '7 · 2a-1',
53
+ confidence: 'verified',
54
+ });
55
+ expect(outcome.ok).toBe(true);
56
+ expect(outcome.redactions).toBe(1);
57
+ const written = await readFile(join(root, '.openagentsy', 'memory.jsonl'), 'utf8');
58
+ expect(written).not.toContain(SEEDED_KEY);
59
+ expect(written).toContain('redacted:aws-access-key');
60
+ }
61
+ finally {
62
+ await remove();
63
+ }
64
+ });
65
+ it('refuses a duplicate with the id of the memory that holds it', async () => {
66
+ const { root, remove } = await temporaryRoot();
67
+ try {
68
+ const store = await storeIn(root);
69
+ const first = await store.append({
70
+ text: 'The lease guard refuses a write to a file no lease covers.',
71
+ category: 'build',
72
+ source: '7 · 2a-1',
73
+ confidence: 'inferred',
74
+ });
75
+ expect(first.ok).toBe(true);
76
+ if (!first.ok)
77
+ return;
78
+ const second = await store.append({
79
+ text: ' The LEASE guard refuses a write to a file no lease covers. ',
80
+ category: 'build',
81
+ source: '8 · 2a-2',
82
+ confidence: 'inferred',
83
+ });
84
+ expect(second.ok).toBe(false);
85
+ if (second.ok)
86
+ throw new Error('the duplicate was accepted');
87
+ expect(second.reason).toBe('duplicate');
88
+ if (second.reason !== 'duplicate')
89
+ throw new Error(`the outcome was ${second.reason}`);
90
+ expect(second.id).toBe(first.entry.id);
91
+ }
92
+ finally {
93
+ await remove();
94
+ }
95
+ });
96
+ it('refuses a text that is too long, and a category that is not a tag', async () => {
97
+ const { root, remove } = await temporaryRoot();
98
+ try {
99
+ const store = await storeIn(root);
100
+ const long = await store.append({
101
+ text: 'x'.repeat(281),
102
+ category: 'build',
103
+ source: '7 · 2a-1',
104
+ confidence: 'guess',
105
+ });
106
+ expect(long.ok).toBe(false);
107
+ if (!long.ok)
108
+ expect(long.reason).toBe('invalid');
109
+ const tagged = await store.append({
110
+ text: 'A short enough text.',
111
+ category: 'Not A Tag',
112
+ source: '7 · 2a-1',
113
+ confidence: 'guess',
114
+ });
115
+ expect(tagged.ok).toBe(false);
116
+ }
117
+ finally {
118
+ await remove();
119
+ }
120
+ });
121
+ it('folds a forget marker into the view and keeps the line in the file', async () => {
122
+ const { root, remove } = await temporaryRoot();
123
+ try {
124
+ const store = await storeIn(root);
125
+ const outcome = await store.append({
126
+ text: 'A memory that is about to be forgotten.',
127
+ category: 'build',
128
+ source: '7 · 2a-1',
129
+ confidence: 'guess',
130
+ });
131
+ expect(outcome.ok).toBe(true);
132
+ if (!outcome.ok)
133
+ return;
134
+ expect(await store.forget(outcome.entry.id, 'daniel')).toBe(true);
135
+ expect(await store.entries()).toEqual([]);
136
+ const lines = (await readFile(join(root, '.openagentsy', 'memory.jsonl'), 'utf8'))
137
+ .trim()
138
+ .split('\n');
139
+ expect(lines).toHaveLength(2);
140
+ expect(JSON.parse(lines[1] ?? '{}')).toMatchObject({
141
+ kind: 'forget',
142
+ id: outcome.entry.id,
143
+ by: 'daniel',
144
+ });
145
+ }
146
+ finally {
147
+ await remove();
148
+ }
149
+ });
150
+ it('marks a memory verified, and audits who did it', async () => {
151
+ const { root, remove } = await temporaryRoot();
152
+ try {
153
+ const audited = [];
154
+ const store = await storeIn(root, (policyId, detail) => {
155
+ audited.push({ policyId, detail });
156
+ });
157
+ const outcome = await store.append({
158
+ text: 'A memory that a person will verify.',
159
+ category: 'build',
160
+ source: '7 · 2a-1',
161
+ confidence: 'guess',
162
+ // A person marked it unverified when it was written; the call
163
+ // below marks it verified.
164
+ verified: false,
165
+ });
166
+ expect(outcome.ok).toBe(true);
167
+ if (!outcome.ok)
168
+ return;
169
+ expect(outcome.entry.verified).toBe(false);
170
+ expect(await store.verify(outcome.entry.id, 'daniel')).toBe(true);
171
+ const entries = await store.entries();
172
+ expect(entries[0]?.verified).toBe(true);
173
+ expect(audited).toEqual([
174
+ { policyId: 'oa.memory.verify', detail: { id: outcome.entry.id, decidedBy: 'daniel' } },
175
+ ]);
176
+ }
177
+ finally {
178
+ await remove();
179
+ }
180
+ });
181
+ it('reads the file once until it changes', async () => {
182
+ const { root, remove } = await temporaryRoot();
183
+ try {
184
+ const store = await storeIn(root);
185
+ await store.append({
186
+ text: 'The first memory.',
187
+ category: 'build',
188
+ source: '7 · 2a-1',
189
+ confidence: 'guess',
190
+ });
191
+ expect(await store.entries()).toHaveLength(1);
192
+ // The cache is keyed on the file's stat, so a line written behind the
193
+ // store's back is seen on the next read.
194
+ const path = join(root, '.openagentsy', 'memory.jsonl');
195
+ const { appendFile } = await import('node:fs/promises');
196
+ await appendFile(path, `${JSON.stringify({
197
+ id: 'oa-mem-external',
198
+ text: 'A memory written by someone else.',
199
+ category: 'build',
200
+ source: '9 · 2a-9',
201
+ confidence: 'guess',
202
+ created_at: '2026-10-26T12:00:00.000Z',
203
+ verified: false,
204
+ })}\n`, 'utf8');
205
+ expect(await store.entries()).toHaveLength(2);
206
+ }
207
+ finally {
208
+ await remove();
209
+ }
210
+ });
211
+ it('has no file before the first memory', async () => {
212
+ const { root, remove } = await temporaryRoot();
213
+ try {
214
+ const store = await storeIn(root);
215
+ expect(existsSync(join(root, '.openagentsy', 'memory.jsonl'))).toBe(false);
216
+ expect(await store.entries()).toEqual([]);
217
+ }
218
+ finally {
219
+ await remove();
220
+ }
221
+ });
222
+ });
223
+ //# sourceMappingURL=store.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"store.test.js","sourceRoot":"","sources":["../src/store.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,86 @@
1
+ import { memoryToolNames, type ToolDef } from '@openagentsy/core';
2
+ import { z } from 'zod';
3
+ import type { MemoryInbox } from './inbox.js';
4
+ import type { MemoryStore } from './store.js';
5
+ /**
6
+ * The memory tools.
7
+ *
8
+ * Three tools: search, list, and the decision. The decision tool exists and
9
+ * works, and the permission map is what keeps an agent from calling it: the
10
+ * map denies `oa_memory_decide` to every role, so only a person, through
11
+ * the command that will call this same code, decides what is kept.
12
+ */
13
+ /**
14
+ * The tool names, for the base-tool vocabulary.
15
+ *
16
+ * The list lives in core, with the other tool names, so that the vocabulary
17
+ * which resolves a tool name back to its base name and the tools the
18
+ * server offers are one list.
19
+ */
20
+ export { memoryToolNames };
21
+ /** The most results a search may return. */
22
+ export declare const MAX_SEARCH_LIMIT = 8;
23
+ /** The results a search returns when the caller names no limit. */
24
+ export declare const DEFAULT_SEARCH_LIMIT = 5;
25
+ /** The most entries a list may return. */
26
+ export declare const MAX_LIST_LIMIT = 20;
27
+ /** The entries a list returns when the caller names no limit. */
28
+ export declare const DEFAULT_LIST_LIMIT = 10;
29
+ /** What the tools need. */
30
+ export type MemoryToolDeps = {
31
+ store: MemoryStore;
32
+ inbox: MemoryInbox;
33
+ };
34
+ /**
35
+ * A memory, as a tool returns it.
36
+ *
37
+ * An unverified memory is redacted: the text becomes a marker that names
38
+ * the id, so the caller can ask for it again and say so. The rest of the
39
+ * entry stays, because what a memory is about is not a secret and a caller
40
+ * that cannot see the category cannot ask for the category.
41
+ */
42
+ export type MemoryHit = {
43
+ id: string;
44
+ text: string;
45
+ category: string;
46
+ source: string;
47
+ confidence: string;
48
+ created_at: string;
49
+ verified: boolean;
50
+ };
51
+ declare const searchArgs: z.ZodObject<{
52
+ q: z.ZodString;
53
+ category: z.ZodOptional<z.ZodString>;
54
+ limit: z.ZodOptional<z.ZodNumber>;
55
+ ordering: z.ZodOptional<z.ZodEnum<{
56
+ relevance: "relevance";
57
+ recency: "recency";
58
+ "lost-in-the-middle": "lost-in-the-middle";
59
+ }>>;
60
+ includeUnverified: z.ZodOptional<z.ZodBoolean>;
61
+ }, z.core.$strip>;
62
+ declare const listArgs: z.ZodObject<{
63
+ limit: z.ZodOptional<z.ZodNumber>;
64
+ }, z.core.$strip>;
65
+ declare const decideArgs: z.ZodObject<{
66
+ recordId: z.ZodNumber;
67
+ decision: z.ZodEnum<{
68
+ approve: "approve";
69
+ reject: "reject";
70
+ }>;
71
+ decidedBy: z.ZodString;
72
+ }, z.core.$strip>;
73
+ /** The three tools, each with the shape of its own arguments. */
74
+ export type MemoryTools = {
75
+ oa_memory_search: ToolDef<typeof searchArgs>;
76
+ oa_memory_list: ToolDef<typeof listArgs>;
77
+ oa_memory_decide: ToolDef<typeof decideArgs>;
78
+ };
79
+ /**
80
+ * The three tools, defined once.
81
+ *
82
+ * The definitions are handed to the two runtimes through the helper in
83
+ * core, so the shape is the same in both OpenCode versions.
84
+ */
85
+ export declare function createMemoryTools(deps: MemoryToolDeps): MemoryTools;
86
+ //# sourceMappingURL=tools.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"tools.d.ts","sourceRoot":"","sources":["../src/tools.ts"],"names":[],"mappings":"AAAA,OAAO,EAAc,eAAe,EAAE,KAAK,OAAO,EAAE,MAAM,mBAAmB,CAAC;AAC9E,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,YAAY,CAAC;AAE9C;;;;;;;GAOG;AAEH;;;;;;GAMG;AACH,OAAO,EAAE,eAAe,EAAE,CAAC;AAE3B,4CAA4C;AAC5C,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,mEAAmE;AACnE,eAAO,MAAM,oBAAoB,IAAI,CAAC;AAEtC,0CAA0C;AAC1C,eAAO,MAAM,cAAc,KAAK,CAAC;AAEjC,iEAAiE;AACjE,eAAO,MAAM,kBAAkB,KAAK,CAAC;AAErC,2BAA2B;AAC3B,MAAM,MAAM,cAAc,GAAG;IAC3B,KAAK,EAAE,WAAW,CAAC;IACnB,KAAK,EAAE,WAAW,CAAC;CACpB,CAAC;AAEF;;;;;;;GAOG;AACH,MAAM,MAAM,SAAS,GAAG;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,OAAO,CAAC;CACnB,CAAC;AAEF,QAAA,MAAM,UAAU;;;;;;;;;;iBAMd,CAAC;AAEH,QAAA,MAAM,QAAQ;;iBAEZ,CAAC;AAEH,QAAA,MAAM,UAAU;;;;;;;iBAId,CAAC;AAEH,iEAAiE;AACjE,MAAM,MAAM,WAAW,GAAG;IACxB,gBAAgB,EAAE,OAAO,CAAC,OAAO,UAAU,CAAC,CAAC;IAC7C,cAAc,EAAE,OAAO,CAAC,OAAO,QAAQ,CAAC,CAAC;IACzC,gBAAgB,EAAE,OAAO,CAAC,OAAO,UAAU,CAAC,CAAC;CAC9C,CAAC;AAEF;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,cAAc,GAAG,WAAW,CA8CnE"}