@sentry/junior-memory 0.76.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/LICENSE +201 -0
- package/dist/agent.d.ts +144 -0
- package/dist/cli/format.d.ts +5 -0
- package/dist/cli/index.d.ts +3 -0
- package/dist/cli/search.d.ts +4 -0
- package/dist/cli/show.d.ts +4 -0
- package/dist/db/schema.d.ts +441 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +1773 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin.d.ts +6 -0
- package/dist/process-session.d.ts +10 -0
- package/dist/recall.d.ts +12 -0
- package/dist/scope.d.ts +17 -0
- package/dist/store.d.ts +93 -0
- package/dist/tools.d.ts +103 -0
- package/dist/types.d.ts +89 -0
- package/migrations/0000_dizzy_millenium_guard.sql +37 -0
- package/migrations/0001_closed_madrox.sql +2 -0
- package/migrations/0002_light_silver_centurion.sql +17 -0
- package/migrations/meta/0000_snapshot.json +234 -0
- package/migrations/meta/0001_snapshot.json +234 -0
- package/migrations/meta/0002_snapshot.json +348 -0
- package/migrations/meta/_journal.json +27 -0
- package/package.json +48 -0
- package/src/agent.ts +437 -0
- package/src/cli/format.ts +30 -0
- package/src/cli/index.ts +15 -0
- package/src/cli/search.ts +119 -0
- package/src/cli/show.ts +44 -0
- package/src/db/schema.ts +130 -0
- package/src/index.ts +16 -0
- package/src/plugin.ts +103 -0
- package/src/process-session.ts +151 -0
- package/src/recall.ts +81 -0
- package/src/scope.ts +99 -0
- package/src/store.ts +761 -0
- package/src/tools.ts +487 -0
- package/src/types.ts +66 -0
package/src/tools.ts
ADDED
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { Type, type TSchema } from "@sinclair/typebox";
|
|
2
|
+
import { Value } from "@sinclair/typebox/value";
|
|
3
|
+
import {
|
|
4
|
+
getSourceKey,
|
|
5
|
+
PluginToolInputError,
|
|
6
|
+
type PluginToolDefinition,
|
|
7
|
+
type Source,
|
|
8
|
+
type Requester,
|
|
9
|
+
} from "@sentry/junior-plugin-api";
|
|
10
|
+
import {
|
|
11
|
+
createMemoryStore,
|
|
12
|
+
type CreateMemoryInput,
|
|
13
|
+
type MemoryEmbeddingProvider,
|
|
14
|
+
type MemoryDb,
|
|
15
|
+
type MemoryRecord,
|
|
16
|
+
} from "./store";
|
|
17
|
+
import {
|
|
18
|
+
parseCreateMemoryRequest,
|
|
19
|
+
parseMemoryReview,
|
|
20
|
+
type MemoryAgent,
|
|
21
|
+
} from "./agent";
|
|
22
|
+
import { memoryRuntimeContextSchema, type MemoryRuntimeContext } from "./types";
|
|
23
|
+
|
|
24
|
+
export type MemoryReviewer = Pick<MemoryAgent, "reviewCreateRequest">;
|
|
25
|
+
|
|
26
|
+
const MAX_TOOL_CONTENT_CHARS = 4_000;
|
|
27
|
+
const DEFAULT_RESULT_LIMIT = 20;
|
|
28
|
+
const DEFAULT_SEARCH_LIMIT = 10;
|
|
29
|
+
|
|
30
|
+
const KNOWN_TOOL_INPUT_ERROR_MESSAGES = new Set([
|
|
31
|
+
"Conversation memory requires conversation context.",
|
|
32
|
+
"Conversation-subject memory requires conversation context.",
|
|
33
|
+
"Memory content is required.",
|
|
34
|
+
"Memory content exceeds the maximum length.",
|
|
35
|
+
"Memory id is required.",
|
|
36
|
+
"Memory was not found in the current context.",
|
|
37
|
+
"Memory id prefix is ambiguous.",
|
|
38
|
+
"Personal memory requires requester context.",
|
|
39
|
+
"User-subject memory requires requester context.",
|
|
40
|
+
]);
|
|
41
|
+
|
|
42
|
+
/** Runtime-owned context used to bind memory tools to visible scopes. */
|
|
43
|
+
export interface MemoryToolContext {
|
|
44
|
+
agent: MemoryReviewer;
|
|
45
|
+
conversationId?: string;
|
|
46
|
+
db: MemoryDb;
|
|
47
|
+
embedder?: MemoryEmbeddingProvider;
|
|
48
|
+
requester?: Requester;
|
|
49
|
+
source: Source;
|
|
50
|
+
userText?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
function throwToolInputError(message: string): never {
|
|
54
|
+
throw new PluginToolInputError(message);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asToolInputError(error: unknown): never {
|
|
58
|
+
if (error instanceof PluginToolInputError) {
|
|
59
|
+
throw error;
|
|
60
|
+
}
|
|
61
|
+
if (
|
|
62
|
+
error instanceof Error &&
|
|
63
|
+
KNOWN_TOOL_INPUT_ERROR_MESSAGES.has(error.message)
|
|
64
|
+
) {
|
|
65
|
+
throw new PluginToolInputError(error.message, { cause: error });
|
|
66
|
+
}
|
|
67
|
+
throw error;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function memoryRuntimeContext(
|
|
71
|
+
context: MemoryToolContext,
|
|
72
|
+
): MemoryRuntimeContext {
|
|
73
|
+
return memoryRuntimeContextSchema.parse({
|
|
74
|
+
...(context.conversationId
|
|
75
|
+
? { conversationId: context.conversationId }
|
|
76
|
+
: {}),
|
|
77
|
+
...(context.requester ? { requester: context.requester } : {}),
|
|
78
|
+
source: context.source,
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function memoryStore(context: MemoryToolContext) {
|
|
83
|
+
return createMemoryStore(context.db, memoryRuntimeContext(context), {
|
|
84
|
+
embedder: context.embedder,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function boundedLimit(value: number | undefined, fallback: number): number {
|
|
89
|
+
if (typeof value !== "number" || !Number.isFinite(value)) {
|
|
90
|
+
return fallback;
|
|
91
|
+
}
|
|
92
|
+
return Math.min(50, Math.max(1, Math.floor(value)));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function digitAt(value: string, index: number): boolean {
|
|
96
|
+
const code = value.charCodeAt(index);
|
|
97
|
+
return code >= 48 && code <= 57;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function readDigits(
|
|
101
|
+
value: string,
|
|
102
|
+
start: number,
|
|
103
|
+
length: number,
|
|
104
|
+
): number | undefined {
|
|
105
|
+
for (let index = start; index < start + length; index++) {
|
|
106
|
+
if (!digitAt(value, index)) {
|
|
107
|
+
return undefined;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
return Number(value.slice(start, start + length));
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function parseIsoTimestampParts(value: string) {
|
|
114
|
+
if (
|
|
115
|
+
value.length < 20 ||
|
|
116
|
+
value[4] !== "-" ||
|
|
117
|
+
value[7] !== "-" ||
|
|
118
|
+
value[10] !== "T" ||
|
|
119
|
+
value[13] !== ":" ||
|
|
120
|
+
value[16] !== ":"
|
|
121
|
+
) {
|
|
122
|
+
return undefined;
|
|
123
|
+
}
|
|
124
|
+
const year = readDigits(value, 0, 4);
|
|
125
|
+
const month = readDigits(value, 5, 2);
|
|
126
|
+
const day = readDigits(value, 8, 2);
|
|
127
|
+
const hour = readDigits(value, 11, 2);
|
|
128
|
+
const minute = readDigits(value, 14, 2);
|
|
129
|
+
const second = readDigits(value, 17, 2);
|
|
130
|
+
if (
|
|
131
|
+
year === undefined ||
|
|
132
|
+
month === undefined ||
|
|
133
|
+
day === undefined ||
|
|
134
|
+
hour === undefined ||
|
|
135
|
+
minute === undefined ||
|
|
136
|
+
second === undefined
|
|
137
|
+
) {
|
|
138
|
+
return undefined;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
let zoneStart = 19;
|
|
142
|
+
if (value[zoneStart] === ".") {
|
|
143
|
+
zoneStart += 1;
|
|
144
|
+
const fractionStart = zoneStart;
|
|
145
|
+
while (zoneStart < value.length && digitAt(value, zoneStart)) {
|
|
146
|
+
zoneStart += 1;
|
|
147
|
+
}
|
|
148
|
+
if (zoneStart === fractionStart) {
|
|
149
|
+
return undefined;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
if (value[zoneStart] === "Z") {
|
|
154
|
+
if (zoneStart !== value.length - 1) {
|
|
155
|
+
return undefined;
|
|
156
|
+
}
|
|
157
|
+
} else if (value[zoneStart] === "+" || value[zoneStart] === "-") {
|
|
158
|
+
if (
|
|
159
|
+
zoneStart !== value.length - 6 ||
|
|
160
|
+
value[zoneStart + 3] !== ":" ||
|
|
161
|
+
readDigits(value, zoneStart + 1, 2) === undefined ||
|
|
162
|
+
readDigits(value, zoneStart + 4, 2) === undefined
|
|
163
|
+
) {
|
|
164
|
+
return undefined;
|
|
165
|
+
}
|
|
166
|
+
} else {
|
|
167
|
+
return undefined;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return { day, hour, minute, month, second, year };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function parseExpiresAt(value: string | undefined): number | undefined {
|
|
174
|
+
if (!value) {
|
|
175
|
+
return undefined;
|
|
176
|
+
}
|
|
177
|
+
if (value === "never") {
|
|
178
|
+
return undefined;
|
|
179
|
+
}
|
|
180
|
+
const parts = parseIsoTimestampParts(value);
|
|
181
|
+
const expiresAtMs = Date.parse(value);
|
|
182
|
+
if (!parts || !Number.isFinite(expiresAtMs)) {
|
|
183
|
+
throwToolInputError('expires_at must be "never" or a valid ISO timestamp.');
|
|
184
|
+
}
|
|
185
|
+
const calendarDate = new Date(
|
|
186
|
+
Date.UTC(parts.year, parts.month - 1, parts.day),
|
|
187
|
+
);
|
|
188
|
+
if (
|
|
189
|
+
calendarDate.getUTCFullYear() !== parts.year ||
|
|
190
|
+
calendarDate.getUTCMonth() !== parts.month - 1 ||
|
|
191
|
+
calendarDate.getUTCDate() !== parts.day ||
|
|
192
|
+
parts.hour > 23 ||
|
|
193
|
+
parts.minute > 59 ||
|
|
194
|
+
parts.second > 59
|
|
195
|
+
) {
|
|
196
|
+
throwToolInputError('expires_at must be "never" or a valid ISO timestamp.');
|
|
197
|
+
}
|
|
198
|
+
return expiresAtMs;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function requireToolCallId(value: string | undefined): string {
|
|
202
|
+
if (!value) {
|
|
203
|
+
throwToolInputError("Memory creation requires a tool call id.");
|
|
204
|
+
}
|
|
205
|
+
return value;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
function requireMemoryContent(value: string): string {
|
|
209
|
+
if (value.trim().length === 0) {
|
|
210
|
+
throwToolInputError("Memory content is required.");
|
|
211
|
+
}
|
|
212
|
+
return value;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
type MemoryWriteToolInput = {
|
|
216
|
+
content: string;
|
|
217
|
+
expires_at?: string;
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const createMemoryInputSchema = Type.Object(
|
|
221
|
+
{
|
|
222
|
+
content: Type.String({
|
|
223
|
+
minLength: 1,
|
|
224
|
+
maxLength: MAX_TOOL_CONTENT_CHARS,
|
|
225
|
+
description:
|
|
226
|
+
"Self-contained public/shareable memory candidate. Include the subject in natural language when it matters; do not rely on surrounding chat context.",
|
|
227
|
+
}),
|
|
228
|
+
expires_at: Type.Optional(
|
|
229
|
+
Type.String({
|
|
230
|
+
minLength: 1,
|
|
231
|
+
description:
|
|
232
|
+
'Expiration selector. Omit or use "never" when the memory should not expire, or use an exact ISO timestamp such as "2027-06-21T00:00:00Z".',
|
|
233
|
+
}),
|
|
234
|
+
),
|
|
235
|
+
},
|
|
236
|
+
{ additionalProperties: false },
|
|
237
|
+
);
|
|
238
|
+
|
|
239
|
+
const removeMemoryInputSchema = Type.Object(
|
|
240
|
+
{
|
|
241
|
+
id: Type.String({
|
|
242
|
+
minLength: 1,
|
|
243
|
+
description: "Memory id or unambiguous short id prefix to remove.",
|
|
244
|
+
}),
|
|
245
|
+
},
|
|
246
|
+
{ additionalProperties: false },
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
const listMemoriesInputSchema = Type.Object(
|
|
250
|
+
{
|
|
251
|
+
limit: Type.Optional(
|
|
252
|
+
Type.Number({
|
|
253
|
+
minimum: 1,
|
|
254
|
+
maximum: 50,
|
|
255
|
+
description: "Maximum number of visible memories to return.",
|
|
256
|
+
}),
|
|
257
|
+
),
|
|
258
|
+
},
|
|
259
|
+
{ additionalProperties: false },
|
|
260
|
+
);
|
|
261
|
+
|
|
262
|
+
const searchMemoriesInputSchema = Type.Object(
|
|
263
|
+
{
|
|
264
|
+
query: Type.String({
|
|
265
|
+
minLength: 1,
|
|
266
|
+
description: "Search query for visible memory content.",
|
|
267
|
+
}),
|
|
268
|
+
limit: Type.Optional(
|
|
269
|
+
Type.Number({
|
|
270
|
+
minimum: 1,
|
|
271
|
+
maximum: 50,
|
|
272
|
+
description: "Maximum number of matching memories to return.",
|
|
273
|
+
}),
|
|
274
|
+
),
|
|
275
|
+
},
|
|
276
|
+
{ additionalProperties: false },
|
|
277
|
+
);
|
|
278
|
+
|
|
279
|
+
function parseToolInput<T>(schema: TSchema, input: unknown): T {
|
|
280
|
+
try {
|
|
281
|
+
if (!Value.Check(schema, input)) {
|
|
282
|
+
throw new Error("Input does not match memory tool schema.");
|
|
283
|
+
}
|
|
284
|
+
return Value.Parse(schema, input) as T;
|
|
285
|
+
} catch (error) {
|
|
286
|
+
throw new PluginToolInputError("Invalid memory tool input.", {
|
|
287
|
+
cause: error,
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
function sourceIdempotencyKey(context: MemoryToolContext): string {
|
|
293
|
+
const sourceKey = getSourceKey(context.source);
|
|
294
|
+
if (!sourceKey) {
|
|
295
|
+
throwToolInputError("Memory creation requires source message context.");
|
|
296
|
+
}
|
|
297
|
+
return sourceKey;
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
function createInput(
|
|
301
|
+
context: MemoryToolContext,
|
|
302
|
+
input: { content: string; expiresAtMs?: number },
|
|
303
|
+
toolCallId: string,
|
|
304
|
+
) {
|
|
305
|
+
return {
|
|
306
|
+
content: requireMemoryContent(input.content),
|
|
307
|
+
idempotencyKey: `tool:${sourceIdempotencyKey(context)}:${toolCallId}`,
|
|
308
|
+
...(input.expiresAtMs !== undefined
|
|
309
|
+
? { expiresAtMs: input.expiresAtMs }
|
|
310
|
+
: {}),
|
|
311
|
+
} satisfies CreateMemoryInput;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/** Return the model-visible projection without hidden ownership/source fields. */
|
|
315
|
+
function compactMemory(memory: MemoryRecord) {
|
|
316
|
+
return {
|
|
317
|
+
id: memory.id,
|
|
318
|
+
content: memory.content,
|
|
319
|
+
createdAtMs: memory.createdAtMs,
|
|
320
|
+
...(memory.expiresAtMs !== undefined
|
|
321
|
+
? { expiresAtMs: memory.expiresAtMs }
|
|
322
|
+
: {}),
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
/** Create a tool that submits an explicit memory candidate for storage. */
|
|
327
|
+
export function createMemoryCreateTool(context: MemoryToolContext) {
|
|
328
|
+
return {
|
|
329
|
+
description:
|
|
330
|
+
"Explicit memory-write tool. Use only when the latest user message directly asks Junior to remember, store, save, or forget-and-replace a public/shareable fact. Do not use for ordinary statements like 'I prefer X', 'I use Y', or 'X goes before Y' unless the user also asks you to remember/store/save it; passive memory learning handles those after the visible reply. Pass one self-contained natural-language candidate preserving the user's explicit memory intent. Do not ask the user to rephrase ordinary first-person facts, and do not rewrite them into display-name or third-person wording. Do not include secrets, private personal details, medical/legal/financial/sensitive facts, or another person's personal preference, opinion, habit, identity, relationship, workflow, or private life. Runtime context derives actor, scope, source, and subject ids; the memory agent decides the canonical stored content, subject, and target.",
|
|
331
|
+
executionMode: "sequential",
|
|
332
|
+
inputSchema: createMemoryInputSchema,
|
|
333
|
+
execute: async (input, options) => {
|
|
334
|
+
const parsedInput = parseToolInput<MemoryWriteToolInput>(
|
|
335
|
+
createMemoryInputSchema,
|
|
336
|
+
input,
|
|
337
|
+
);
|
|
338
|
+
const toolCallId = requireToolCallId(options.toolCallId);
|
|
339
|
+
const requestedExpiresAtMs = parseExpiresAt(parsedInput.expires_at);
|
|
340
|
+
const runtimeContext = memoryRuntimeContext(context);
|
|
341
|
+
const store = memoryStore(context);
|
|
342
|
+
const review = await (async () => {
|
|
343
|
+
try {
|
|
344
|
+
return parseMemoryReview(
|
|
345
|
+
await context.agent.reviewCreateRequest(
|
|
346
|
+
parseCreateMemoryRequest({
|
|
347
|
+
content: requireMemoryContent(parsedInput.content),
|
|
348
|
+
...(requestedExpiresAtMs !== undefined
|
|
349
|
+
? { expiresAtMs: requestedExpiresAtMs }
|
|
350
|
+
: {}),
|
|
351
|
+
runtimeContext,
|
|
352
|
+
...(context.userText?.trim()
|
|
353
|
+
? {
|
|
354
|
+
sourceContext: {
|
|
355
|
+
currentUserText: context.userText.trim(),
|
|
356
|
+
},
|
|
357
|
+
}
|
|
358
|
+
: {}),
|
|
359
|
+
}),
|
|
360
|
+
),
|
|
361
|
+
);
|
|
362
|
+
} catch (error) {
|
|
363
|
+
if (error instanceof PluginToolInputError) {
|
|
364
|
+
throw error;
|
|
365
|
+
}
|
|
366
|
+
const detail =
|
|
367
|
+
error instanceof Error && error.message.trim()
|
|
368
|
+
? `: ${error.message}`
|
|
369
|
+
: "";
|
|
370
|
+
throw new PluginToolInputError(
|
|
371
|
+
`Memory agent review failed${detail}`,
|
|
372
|
+
{ cause: error },
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
})();
|
|
376
|
+
if (review.decision === "reject") {
|
|
377
|
+
throw new PluginToolInputError(
|
|
378
|
+
`Memory was not stored: ${review.reason}`,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
const memoryInput = createInput(
|
|
382
|
+
context,
|
|
383
|
+
{
|
|
384
|
+
content: review.content,
|
|
385
|
+
...(review.expiresAtMs !== undefined
|
|
386
|
+
? { expiresAtMs: review.expiresAtMs }
|
|
387
|
+
: requestedExpiresAtMs !== undefined
|
|
388
|
+
? { expiresAtMs: requestedExpiresAtMs }
|
|
389
|
+
: {}),
|
|
390
|
+
},
|
|
391
|
+
toolCallId,
|
|
392
|
+
);
|
|
393
|
+
const result = await (async () => {
|
|
394
|
+
try {
|
|
395
|
+
if (review.target === "conversation") {
|
|
396
|
+
return await store.createConversationMemory(memoryInput);
|
|
397
|
+
}
|
|
398
|
+
return await store.createMemory(memoryInput);
|
|
399
|
+
} catch (error) {
|
|
400
|
+
asToolInputError(error);
|
|
401
|
+
}
|
|
402
|
+
})();
|
|
403
|
+
return {
|
|
404
|
+
ok: true,
|
|
405
|
+
created: result.created,
|
|
406
|
+
memory: compactMemory(result.memory),
|
|
407
|
+
};
|
|
408
|
+
},
|
|
409
|
+
} satisfies PluginToolDefinition<MemoryWriteToolInput>;
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/** Create a tool that archives a visible memory in the active context. */
|
|
413
|
+
export function createMemoryRemoveTool(context: MemoryToolContext) {
|
|
414
|
+
return {
|
|
415
|
+
description:
|
|
416
|
+
"Forget one memory visible in the active context. Use only ids or short id prefixes returned by listMemories or searchMemories. Never remove memories by hidden actor, Slack, scope, or subject identifiers.",
|
|
417
|
+
executionMode: "sequential",
|
|
418
|
+
inputSchema: removeMemoryInputSchema,
|
|
419
|
+
execute: async (input) => {
|
|
420
|
+
const parsedInput = parseToolInput<{ id: string }>(
|
|
421
|
+
removeMemoryInputSchema,
|
|
422
|
+
input,
|
|
423
|
+
);
|
|
424
|
+
const memory = await (async () => {
|
|
425
|
+
try {
|
|
426
|
+
return await memoryStore(context).archiveMemory({
|
|
427
|
+
id: parsedInput.id,
|
|
428
|
+
reason: "tool_removed",
|
|
429
|
+
});
|
|
430
|
+
} catch (error) {
|
|
431
|
+
asToolInputError(error);
|
|
432
|
+
}
|
|
433
|
+
})();
|
|
434
|
+
return {
|
|
435
|
+
ok: true,
|
|
436
|
+
memory: compactMemory(memory),
|
|
437
|
+
};
|
|
438
|
+
},
|
|
439
|
+
} satisfies PluginToolDefinition<{ id: string }>;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Create a tool that lists visible active memories in the active context. */
|
|
443
|
+
export function createMemoryListTool(context: MemoryToolContext) {
|
|
444
|
+
return {
|
|
445
|
+
description:
|
|
446
|
+
"List active memories visible in the current context. Use when the user asks what Junior remembers or when memory ids are needed before removing a memory.",
|
|
447
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
448
|
+
inputSchema: listMemoriesInputSchema,
|
|
449
|
+
execute: async (input) => {
|
|
450
|
+
const parsedInput = parseToolInput<{ limit?: number }>(
|
|
451
|
+
listMemoriesInputSchema,
|
|
452
|
+
input,
|
|
453
|
+
);
|
|
454
|
+
const memories = await memoryStore(context).listMemories({
|
|
455
|
+
limit: boundedLimit(parsedInput.limit, DEFAULT_RESULT_LIMIT),
|
|
456
|
+
});
|
|
457
|
+
return {
|
|
458
|
+
ok: true,
|
|
459
|
+
memories: memories.map(compactMemory),
|
|
460
|
+
};
|
|
461
|
+
},
|
|
462
|
+
} satisfies PluginToolDefinition<{ limit?: number }>;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/** Create a tool that searches visible active memories in the active context. */
|
|
466
|
+
export function createMemorySearchTool(context: MemoryToolContext) {
|
|
467
|
+
return {
|
|
468
|
+
description:
|
|
469
|
+
"Search active memories visible in the current context. Use when the model needs targeted memory recall. The tool searches only the current requester and active conversation scopes.",
|
|
470
|
+
annotations: { readOnlyHint: true, destructiveHint: false },
|
|
471
|
+
inputSchema: searchMemoriesInputSchema,
|
|
472
|
+
execute: async (input) => {
|
|
473
|
+
const parsedInput = parseToolInput<{ limit?: number; query: string }>(
|
|
474
|
+
searchMemoriesInputSchema,
|
|
475
|
+
input,
|
|
476
|
+
);
|
|
477
|
+
const memories = await memoryStore(context).searchMemories({
|
|
478
|
+
query: parsedInput.query,
|
|
479
|
+
limit: boundedLimit(parsedInput.limit, DEFAULT_SEARCH_LIMIT),
|
|
480
|
+
});
|
|
481
|
+
return {
|
|
482
|
+
ok: true,
|
|
483
|
+
memories: memories.map(compactMemory),
|
|
484
|
+
};
|
|
485
|
+
},
|
|
486
|
+
} satisfies PluginToolDefinition<{ limit?: number; query: string }>;
|
|
487
|
+
}
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
localRequesterSchema,
|
|
3
|
+
localSourceSchema,
|
|
4
|
+
platformSchema,
|
|
5
|
+
slackRequesterSchema,
|
|
6
|
+
slackSourceSchema,
|
|
7
|
+
} from "@sentry/junior-plugin-api";
|
|
8
|
+
import { z } from "zod";
|
|
9
|
+
|
|
10
|
+
export const MEMORY_TYPES = [
|
|
11
|
+
"preference",
|
|
12
|
+
"identity",
|
|
13
|
+
"relationship",
|
|
14
|
+
"knowledge",
|
|
15
|
+
"context",
|
|
16
|
+
"event",
|
|
17
|
+
"task",
|
|
18
|
+
"observation",
|
|
19
|
+
] as const;
|
|
20
|
+
|
|
21
|
+
export const MEMORY_SCOPES = ["personal", "conversation"] as const;
|
|
22
|
+
export const MEMORY_SUBJECT_TYPES = [
|
|
23
|
+
"user",
|
|
24
|
+
"conversation",
|
|
25
|
+
"general",
|
|
26
|
+
] as const;
|
|
27
|
+
export const MEMORY_SOURCE_PLATFORMS = [
|
|
28
|
+
"slack",
|
|
29
|
+
"local",
|
|
30
|
+
] as const satisfies readonly z.output<typeof platformSchema>[];
|
|
31
|
+
export const MEMORY_EMBEDDING_METRICS = ["cosine"] as const;
|
|
32
|
+
export const MEMORY_EMBEDDING_DIMENSIONS = 1536;
|
|
33
|
+
|
|
34
|
+
export type MemoryType = (typeof MEMORY_TYPES)[number];
|
|
35
|
+
export type MemoryScope = (typeof MEMORY_SCOPES)[number];
|
|
36
|
+
export type MemorySubjectType = (typeof MEMORY_SUBJECT_TYPES)[number];
|
|
37
|
+
export type MemorySourcePlatform = (typeof MEMORY_SOURCE_PLATFORMS)[number];
|
|
38
|
+
export type MemoryEmbeddingMetric = (typeof MEMORY_EMBEDDING_METRICS)[number];
|
|
39
|
+
|
|
40
|
+
const nonEmptyStringSchema = z.string().min(1);
|
|
41
|
+
|
|
42
|
+
/** Runtime-owned memory invocation fields used for scope and source authority. */
|
|
43
|
+
export const slackMemoryRuntimeContextSchema = z
|
|
44
|
+
.object({
|
|
45
|
+
conversationId: nonEmptyStringSchema.optional(),
|
|
46
|
+
requester: slackRequesterSchema.optional(),
|
|
47
|
+
source: slackSourceSchema,
|
|
48
|
+
})
|
|
49
|
+
.strict();
|
|
50
|
+
|
|
51
|
+
/** Runtime-owned local memory invocation fields used for scope and source authority. */
|
|
52
|
+
export const localMemoryRuntimeContextSchema = z
|
|
53
|
+
.object({
|
|
54
|
+
conversationId: nonEmptyStringSchema.optional(),
|
|
55
|
+
requester: localRequesterSchema.optional(),
|
|
56
|
+
source: localSourceSchema,
|
|
57
|
+
})
|
|
58
|
+
.strict();
|
|
59
|
+
|
|
60
|
+
/** Runtime-owned memory invocation fields accepted by memory store operations. */
|
|
61
|
+
export const memoryRuntimeContextSchema = z.union([
|
|
62
|
+
slackMemoryRuntimeContextSchema,
|
|
63
|
+
localMemoryRuntimeContextSchema,
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
export type MemoryRuntimeContext = z.output<typeof memoryRuntimeContextSchema>;
|