@tuturuuu/ai 0.0.10

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 (130) hide show
  1. package/README.md +76 -0
  2. package/package.json +106 -0
  3. package/src/api-key-hash.ts +28 -0
  4. package/src/calendar/events.ts +34 -0
  5. package/src/calendar/route.ts +114 -0
  6. package/src/chat/credit-source.ts +1 -0
  7. package/src/chat/google/chat-request-schema.ts +150 -0
  8. package/src/chat/google/default-system-instruction.ts +198 -0
  9. package/src/chat/google/message-file-processing.ts +212 -0
  10. package/src/chat/google/mira-step-preparation.ts +221 -0
  11. package/src/chat/google/new/route.ts +368 -0
  12. package/src/chat/google/route-auth.ts +81 -0
  13. package/src/chat/google/route-chat-resolution.ts +98 -0
  14. package/src/chat/google/route-credits.ts +61 -0
  15. package/src/chat/google/route-message-preparation.ts +331 -0
  16. package/src/chat/google/route-mira-runtime.ts +206 -0
  17. package/src/chat/google/route.ts +632 -0
  18. package/src/chat/google/stream-finish-persistence.ts +722 -0
  19. package/src/chat/google/summary/route.ts +153 -0
  20. package/src/chat/mira-render-ui-policy.ts +540 -0
  21. package/src/chat/mira-system-instruction.ts +484 -0
  22. package/src/chat-sdk/adapters.ts +389 -0
  23. package/src/chat-sdk/registry.ts +197 -0
  24. package/src/chat-sdk.ts +33 -0
  25. package/src/core.ts +3 -0
  26. package/src/credits/cap-output-tokens.ts +90 -0
  27. package/src/credits/check-credits.ts +232 -0
  28. package/src/credits/constants.ts +30 -0
  29. package/src/credits/index.ts +46 -0
  30. package/src/credits/model-mapping.ts +92 -0
  31. package/src/credits/reservations.ts +514 -0
  32. package/src/credits/resolve-plan-model.ts +219 -0
  33. package/src/credits/sync-gateway-models.ts +351 -0
  34. package/src/credits/types.ts +109 -0
  35. package/src/credits/use-ai-credits.ts +3 -0
  36. package/src/embeddings/metered.ts +283 -0
  37. package/src/executions/route.ts +137 -0
  38. package/src/generate/route.ts +411 -0
  39. package/src/hooks.ts +7 -0
  40. package/src/meetings/summary/route.ts +7 -0
  41. package/src/meetings/transcription/route.ts +134 -0
  42. package/src/memory/client.ts +158 -0
  43. package/src/memory/config.ts +38 -0
  44. package/src/memory/index.ts +32 -0
  45. package/src/memory/ingest.ts +51 -0
  46. package/src/memory/middleware.ts +35 -0
  47. package/src/memory/operations.ts +480 -0
  48. package/src/memory/scope.ts +102 -0
  49. package/src/memory/settings.ts +121 -0
  50. package/src/memory/types.ts +101 -0
  51. package/src/memory/workspace.ts +36 -0
  52. package/src/memory.ts +1 -0
  53. package/src/mind/patch.ts +146 -0
  54. package/src/mind/route.ts +687 -0
  55. package/src/mind/tools.ts +1500 -0
  56. package/src/mind/types.ts +20 -0
  57. package/src/object/core.ts +3 -0
  58. package/src/object/flashcards/route.ts +140 -0
  59. package/src/object/quizzes/explanation/route.ts +145 -0
  60. package/src/object/quizzes/route.ts +142 -0
  61. package/src/object/types.ts +187 -0
  62. package/src/object/year-plan/route.ts +196 -0
  63. package/src/react.ts +1 -0
  64. package/src/scheduling/algorithm.ts +791 -0
  65. package/src/scheduling/default.ts +36 -0
  66. package/src/scheduling/duration-optimizer.ts +689 -0
  67. package/src/scheduling/index.ts +79 -0
  68. package/src/scheduling/priority-calculator.ts +187 -0
  69. package/src/scheduling/recurrence-calculator.ts +621 -0
  70. package/src/scheduling/templates.ts +892 -0
  71. package/src/scheduling/types.ts +136 -0
  72. package/src/scheduling/web-adapter.ts +308 -0
  73. package/src/scheduling.ts +6 -0
  74. package/src/supported-actions.ts +1 -0
  75. package/src/supported-providers.ts +6 -0
  76. package/src/tools/context-builder.ts +372 -0
  77. package/src/tools/core.ts +1 -0
  78. package/src/tools/definitions/calendar.ts +106 -0
  79. package/src/tools/definitions/finance.ts +197 -0
  80. package/src/tools/definitions/image.ts +74 -0
  81. package/src/tools/definitions/memory.ts +83 -0
  82. package/src/tools/definitions/meta.ts +154 -0
  83. package/src/tools/definitions/render-ui.ts +81 -0
  84. package/src/tools/definitions/tasks.ts +343 -0
  85. package/src/tools/definitions/time-tracking.ts +381 -0
  86. package/src/tools/definitions/workspace-context.ts +45 -0
  87. package/src/tools/definitions/workspace-user-chat.ts +111 -0
  88. package/src/tools/executors/calendar.ts +371 -0
  89. package/src/tools/executors/chat.ts +15 -0
  90. package/src/tools/executors/finance.ts +638 -0
  91. package/src/tools/executors/helpers/encryption.ts +107 -0
  92. package/src/tools/executors/image.ts +247 -0
  93. package/src/tools/executors/markitdown.ts +684 -0
  94. package/src/tools/executors/memory.ts +277 -0
  95. package/src/tools/executors/parallel-checks.ts +176 -0
  96. package/src/tools/executors/qr.ts +170 -0
  97. package/src/tools/executors/scope-helpers.ts +192 -0
  98. package/src/tools/executors/search.ts +149 -0
  99. package/src/tools/executors/settings.ts +40 -0
  100. package/src/tools/executors/tasks.ts +1087 -0
  101. package/src/tools/executors/theme.ts +23 -0
  102. package/src/tools/executors/timer/timer-categories-executor.ts +110 -0
  103. package/src/tools/executors/timer/timer-category-mutations.ts +240 -0
  104. package/src/tools/executors/timer/timer-goal-mutations.ts +323 -0
  105. package/src/tools/executors/timer/timer-goals-executor.ts +272 -0
  106. package/src/tools/executors/timer/timer-helpers.ts +372 -0
  107. package/src/tools/executors/timer/timer-mutation-schemas.ts +160 -0
  108. package/src/tools/executors/timer/timer-mutation-types.ts +212 -0
  109. package/src/tools/executors/timer/timer-mutations.ts +19 -0
  110. package/src/tools/executors/timer/timer-queries.ts +18 -0
  111. package/src/tools/executors/timer/timer-session-lifecycle.ts +299 -0
  112. package/src/tools/executors/timer/timer-session-mutations.ts +10 -0
  113. package/src/tools/executors/timer/timer-session-queries.ts +153 -0
  114. package/src/tools/executors/timer/timer-session-updates.ts +200 -0
  115. package/src/tools/executors/timer/timer-sessions-executor.ts +91 -0
  116. package/src/tools/executors/timer/timer-stats-executor.ts +157 -0
  117. package/src/tools/executors/timer.ts +22 -0
  118. package/src/tools/executors/user.ts +60 -0
  119. package/src/tools/executors/workspace.ts +135 -0
  120. package/src/tools/json-render-catalog.ts +875 -0
  121. package/src/tools/mira-tool-definitions.ts +55 -0
  122. package/src/tools/mira-tool-dispatcher.ts +265 -0
  123. package/src/tools/mira-tool-metadata.ts +164 -0
  124. package/src/tools/mira-tool-names.ts +95 -0
  125. package/src/tools/mira-tool-render-ui.ts +54 -0
  126. package/src/tools/mira-tool-types.ts +17 -0
  127. package/src/tools/mira-tools.ts +167 -0
  128. package/src/tools/normalize-render-ui-input.ts +321 -0
  129. package/src/tools/workspace-context.ts +233 -0
  130. package/src/types.ts +38 -0
@@ -0,0 +1,38 @@
1
+ import type { AiMemoryConfig } from './types';
2
+
3
+ const DEFAULT_TIMEOUT_MS = 1500;
4
+
5
+ function parseBoolean(value: string | undefined, fallback: boolean) {
6
+ if (!value) return fallback;
7
+ return ['1', 'true', 'yes', 'on'].includes(value.toLowerCase());
8
+ }
9
+
10
+ function parsePositiveInt(value: string | undefined, fallback: number) {
11
+ if (!value) return fallback;
12
+ const parsed = Number.parseInt(value, 10);
13
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
14
+ }
15
+
16
+ export function getAiMemoryConfig(): AiMemoryConfig | null {
17
+ const apiKey = process.env.SUPERMEMORY_API_KEY?.trim();
18
+ const enabled = parseBoolean(process.env.SUPERMEMORY_ENABLED, false);
19
+
20
+ if (!enabled || !apiKey) return null;
21
+
22
+ const baseUrl = process.env.SUPERMEMORY_BASE_URL?.trim();
23
+
24
+ return {
25
+ apiKey,
26
+ ...(baseUrl ? { baseUrl } : {}),
27
+ enabled,
28
+ failOpen: parseBoolean(process.env.SUPERMEMORY_FAIL_OPEN, true),
29
+ timeoutMs: parsePositiveInt(
30
+ process.env.SUPERMEMORY_TIMEOUT_MS,
31
+ DEFAULT_TIMEOUT_MS
32
+ ),
33
+ };
34
+ }
35
+
36
+ export function isAiMemoryConfigured() {
37
+ return !!getAiMemoryConfig();
38
+ }
@@ -0,0 +1,32 @@
1
+ export { getAiMemoryServiceClient, getSupermemoryClient } from './client';
2
+ export { getAiMemoryConfig, isAiMemoryConfigured } from './config';
3
+ export { ingestAiMemoryEvent } from './ingest';
4
+ export { withAiMemory } from './middleware';
5
+ export {
6
+ buildAiMemoryContext,
7
+ forgetAiMemory,
8
+ listAiMemories,
9
+ rememberAiMemory,
10
+ searchAiMemories,
11
+ } from './operations';
12
+ export {
13
+ buildKeyFilter,
14
+ buildProductFilter,
15
+ resolveAiMemoryScope,
16
+ } from './scope';
17
+ export { getAiMemorySettings, isAiMemoryEnabledForScope } from './settings';
18
+ export type {
19
+ AiMemoryConfig,
20
+ AiMemoryDocument,
21
+ AiMemoryMetadata,
22
+ AiMemoryMetadataValue,
23
+ AiMemoryModelOptions,
24
+ AiMemoryProduct,
25
+ AiMemoryResult,
26
+ AiMemoryScope,
27
+ AiMemoryScopeInput,
28
+ AiMemorySearchResult,
29
+ AiMemorySettings,
30
+ } from './types';
31
+ export { AI_MEMORY_PRODUCTS } from './types';
32
+ export { resolveAiMemoryWorkspaceIdForUser } from './workspace';
@@ -0,0 +1,51 @@
1
+ import { rememberAiMemory } from './operations';
2
+ import { resolveAiMemoryScope } from './scope';
3
+ import type {
4
+ AiMemoryMetadata,
5
+ AiMemoryProduct,
6
+ AiMemoryResult,
7
+ } from './types';
8
+
9
+ export async function ingestAiMemoryEvent({
10
+ content,
11
+ customId,
12
+ metadata,
13
+ product,
14
+ source,
15
+ surface,
16
+ title,
17
+ userId,
18
+ wsId,
19
+ }: {
20
+ content: string;
21
+ customId?: string | null;
22
+ metadata?: AiMemoryMetadata;
23
+ product: AiMemoryProduct;
24
+ source?: string | null;
25
+ surface: string;
26
+ title?: string | null;
27
+ userId?: string | null;
28
+ wsId?: string | null;
29
+ }): Promise<AiMemoryResult<{ id: string; status: string } | null>> {
30
+ const value = content.trim();
31
+ if (!value) {
32
+ return { ok: true, reason: 'empty_content', skipped: true, value: null };
33
+ }
34
+
35
+ const scope = resolveAiMemoryScope({
36
+ customId,
37
+ metadata,
38
+ product,
39
+ source,
40
+ surface,
41
+ userId,
42
+ wsId,
43
+ });
44
+
45
+ return rememberAiMemory({
46
+ category: 'conversation_topic',
47
+ key: title ?? surface,
48
+ scope,
49
+ value,
50
+ });
51
+ }
@@ -0,0 +1,35 @@
1
+ import type { LanguageModel } from 'ai';
2
+ import { getAiMemoryConfig } from './config';
3
+ import { resolveAiMemoryScope } from './scope';
4
+ import type { AiMemoryModelOptions } from './types';
5
+
6
+ export async function withAiMemory<TModel extends LanguageModel>({
7
+ customId,
8
+ model,
9
+ product,
10
+ source,
11
+ surface,
12
+ userId,
13
+ wsId,
14
+ }: AiMemoryModelOptions<TModel>): Promise<TModel> {
15
+ const config = getAiMemoryConfig();
16
+ const scope = resolveAiMemoryScope({
17
+ customId,
18
+ product,
19
+ source,
20
+ surface,
21
+ userId,
22
+ wsId,
23
+ });
24
+ if (!config || !scope) return model;
25
+
26
+ const { isAiMemoryEnabledForScope } = await import('./settings');
27
+ const enabled = await isAiMemoryEnabledForScope({
28
+ product,
29
+ userId: scope.userId,
30
+ wsId: scope.wsId,
31
+ });
32
+ if (!enabled) return model;
33
+
34
+ return model;
35
+ }
@@ -0,0 +1,480 @@
1
+ import {
2
+ createMeteredTextEmbedding,
3
+ shouldDisableMemoryForMeteringReason,
4
+ } from '../embeddings/metered';
5
+ import { getAiMemoryServiceClient } from './client';
6
+ import { getAiMemoryConfig } from './config';
7
+ import { buildProductFilter } from './scope';
8
+ import type {
9
+ AiMemoryConfig,
10
+ AiMemoryDocument,
11
+ AiMemoryMetadata,
12
+ AiMemoryResult,
13
+ AiMemoryScope,
14
+ AiMemorySearchResult,
15
+ } from './types';
16
+
17
+ const DEFAULT_CONTEXT_LIMIT = 8;
18
+
19
+ function skipped<T>(reason: string, value?: T): AiMemoryResult<T> {
20
+ return { ok: true, reason, skipped: true, value };
21
+ }
22
+
23
+ function fail<T>(
24
+ error: unknown,
25
+ fallback: T,
26
+ failOpen: boolean
27
+ ): AiMemoryResult<T> {
28
+ const message = error instanceof Error ? error.message : String(error);
29
+ if (failOpen) return skipped(message, fallback);
30
+ return { error: message, ok: false };
31
+ }
32
+
33
+ function requestOptions(timeoutMs: number) {
34
+ return {
35
+ timeout: timeoutMs,
36
+ };
37
+ }
38
+
39
+ type AiMemoryClient = NonNullable<ReturnType<typeof getAiMemoryServiceClient>>;
40
+
41
+ type AiMemorySearchResultLike = {
42
+ chunk?: string;
43
+ id: string;
44
+ memory?: string;
45
+ metadata?: Record<string, unknown> | null;
46
+ similarity?: number;
47
+ updatedAt?: string;
48
+ };
49
+
50
+ type AiMemoryDocumentLike = {
51
+ content?: string | null;
52
+ id: string;
53
+ metadata?: unknown;
54
+ status: string;
55
+ summary?: string | null;
56
+ title?: string | null;
57
+ updatedAt: string;
58
+ };
59
+
60
+ type AiMemoryRuntime<T> =
61
+ | {
62
+ client: AiMemoryClient;
63
+ config: AiMemoryConfig;
64
+ ready: true;
65
+ scope: AiMemoryScope;
66
+ }
67
+ | {
68
+ client?: never;
69
+ config?: never;
70
+ ready: false;
71
+ scope?: never;
72
+ skip: AiMemoryResult<T>;
73
+ };
74
+
75
+ function buildMemoryContent({
76
+ category,
77
+ key,
78
+ value,
79
+ }: {
80
+ category?: string | null;
81
+ key?: string | null;
82
+ value: string;
83
+ }) {
84
+ return [category ? `[${category}]` : null, key ? `${key}:` : null, value]
85
+ .filter(Boolean)
86
+ .join(' ');
87
+ }
88
+
89
+ function categoryFilter(category: string) {
90
+ return {
91
+ filterType: 'metadata' as const,
92
+ key: 'memoryCategory',
93
+ value: category,
94
+ };
95
+ }
96
+
97
+ function normalizeSearchResult(
98
+ result: AiMemorySearchResultLike
99
+ ): AiMemorySearchResult {
100
+ const metadata = (result.metadata ?? null) as AiMemoryMetadata | null;
101
+ return {
102
+ id: result.id,
103
+ key:
104
+ typeof metadata?.memoryKey === 'string' ? metadata.memoryKey : undefined,
105
+ metadata,
106
+ score: result.similarity ?? 0,
107
+ updatedAt: result.updatedAt ?? new Date(0).toISOString(),
108
+ value: result.memory ?? result.chunk ?? '',
109
+ };
110
+ }
111
+
112
+ function normalizeDocument(doc: AiMemoryDocumentLike): AiMemoryDocument {
113
+ const metadata =
114
+ doc.metadata &&
115
+ typeof doc.metadata === 'object' &&
116
+ !Array.isArray(doc.metadata)
117
+ ? (doc.metadata as AiMemoryMetadata)
118
+ : null;
119
+ return {
120
+ category:
121
+ typeof metadata?.memoryCategory === 'string'
122
+ ? metadata.memoryCategory
123
+ : null,
124
+ content: doc.content,
125
+ id: doc.id,
126
+ key:
127
+ typeof metadata?.memoryKey === 'string' ? metadata.memoryKey : undefined,
128
+ metadata,
129
+ status: doc.status,
130
+ summary: doc.summary,
131
+ title: doc.title,
132
+ updatedAt: doc.updatedAt,
133
+ };
134
+ }
135
+
136
+ async function ensureMemoryRuntime<T>({
137
+ fallback,
138
+ ignoreSettings = false,
139
+ scope,
140
+ }: {
141
+ fallback: T;
142
+ ignoreSettings?: boolean;
143
+ scope: AiMemoryScope | null;
144
+ }): Promise<AiMemoryRuntime<T>> {
145
+ const config = getAiMemoryConfig();
146
+ const client = getAiMemoryServiceClient(config);
147
+ if (!scope || !config || !client) {
148
+ return {
149
+ ready: false,
150
+ skip: skipped('ai_memory_service_not_configured', fallback),
151
+ };
152
+ }
153
+
154
+ if (!ignoreSettings) {
155
+ const { isAiMemoryEnabledForScope } = await import('./settings');
156
+ const enabled = await isAiMemoryEnabledForScope({
157
+ product: scope.product,
158
+ userId: scope.userId,
159
+ wsId: scope.wsId,
160
+ });
161
+ if (!enabled) {
162
+ return {
163
+ ready: false,
164
+ skip: skipped('ai_memory_disabled', fallback),
165
+ };
166
+ }
167
+ }
168
+
169
+ return { client, config, ready: true, scope };
170
+ }
171
+
172
+ async function disableMemoryIfStructuralFailure(
173
+ scope: AiMemoryScope,
174
+ reason: string
175
+ ) {
176
+ const { disableAiMemoryForMeteringFailure } = await import('./settings');
177
+ await disableAiMemoryForMeteringFailure({
178
+ reason,
179
+ userId: scope.userId,
180
+ wsId: scope.wsId,
181
+ });
182
+ }
183
+
184
+ async function createMemoryEmbedding({
185
+ operation,
186
+ scope,
187
+ value,
188
+ }: {
189
+ operation: 'remember' | 'search';
190
+ scope: AiMemoryScope;
191
+ value: string;
192
+ }) {
193
+ const embedding = await createMeteredTextEmbedding({
194
+ metadata: {
195
+ operation,
196
+ product: scope.product,
197
+ },
198
+ source: 'ai_memory',
199
+ taskType: operation === 'search' ? 'RETRIEVAL_QUERY' : 'RETRIEVAL_DOCUMENT',
200
+ userId: scope.userId,
201
+ value,
202
+ wsId: scope.wsId,
203
+ });
204
+
205
+ if (!embedding.ok && shouldDisableMemoryForMeteringReason(embedding)) {
206
+ await disableMemoryIfStructuralFailure(scope, embedding.reason);
207
+ }
208
+
209
+ return embedding;
210
+ }
211
+
212
+ export async function rememberAiMemory({
213
+ category,
214
+ ignoreSettings,
215
+ key,
216
+ scope,
217
+ value,
218
+ }: {
219
+ category?: string | null;
220
+ ignoreSettings?: boolean;
221
+ key?: string | null;
222
+ scope: AiMemoryScope | null;
223
+ value: string;
224
+ }): Promise<AiMemoryResult<{ id: string; status: string } | null>> {
225
+ const runtime = await ensureMemoryRuntime<{
226
+ id: string;
227
+ status: string;
228
+ } | null>({
229
+ fallback: null,
230
+ ignoreSettings,
231
+ scope,
232
+ });
233
+ if (!runtime.ready) return runtime.skip;
234
+
235
+ try {
236
+ const content = buildMemoryContent({ category, key, value });
237
+ const meteredEmbedding = await createMemoryEmbedding({
238
+ operation: 'remember',
239
+ scope: runtime.scope,
240
+ value: content,
241
+ });
242
+ if (!meteredEmbedding.ok) {
243
+ return skipped(meteredEmbedding.reason, null);
244
+ }
245
+
246
+ const metadata: AiMemoryMetadata = {
247
+ ...runtime.scope.metadata,
248
+ ...(category ? { memoryCategory: category } : {}),
249
+ ...(key ? { memoryKey: key } : {}),
250
+ };
251
+ const response = await runtime.client.add(
252
+ {
253
+ containerTag: runtime.scope.containerTag,
254
+ content,
255
+ customId: runtime.scope.customId,
256
+ embedding: meteredEmbedding.embedding,
257
+ metadata,
258
+ },
259
+ requestOptions(runtime.config.timeoutMs)
260
+ );
261
+
262
+ return { ok: true, value: response };
263
+ } catch (error) {
264
+ return fail(error, null, runtime.config.failOpen);
265
+ }
266
+ }
267
+
268
+ export async function searchAiMemories({
269
+ category,
270
+ ignoreSettings,
271
+ includeProductFilter = true,
272
+ limit = DEFAULT_CONTEXT_LIMIT,
273
+ query,
274
+ scope,
275
+ }: {
276
+ category?: string | null;
277
+ ignoreSettings?: boolean;
278
+ includeProductFilter?: boolean;
279
+ limit?: number;
280
+ query: string;
281
+ scope: AiMemoryScope | null;
282
+ }): Promise<AiMemoryResult<AiMemorySearchResult[]>> {
283
+ const runtime = await ensureMemoryRuntime<AiMemorySearchResult[]>({
284
+ fallback: [],
285
+ ignoreSettings,
286
+ scope,
287
+ });
288
+ if (!runtime.ready) return runtime.skip;
289
+
290
+ try {
291
+ const searchQuery =
292
+ query.trim() || 'user preferences facts conversation history';
293
+ const meteredEmbedding = await createMemoryEmbedding({
294
+ operation: 'search',
295
+ scope: runtime.scope,
296
+ value: searchQuery,
297
+ });
298
+ if (!meteredEmbedding.ok) {
299
+ return skipped(meteredEmbedding.reason, []);
300
+ }
301
+
302
+ const filters = [
303
+ includeProductFilter ? buildProductFilter(runtime.scope.product) : null,
304
+ category ? categoryFilter(category) : null,
305
+ ].filter(Boolean);
306
+ const response = await runtime.client.searchMemories(
307
+ {
308
+ containerTag: runtime.scope.containerTag,
309
+ embedding: meteredEmbedding.embedding,
310
+ filters: (filters.length > 1 ? { AND: filters } : filters[0]) as never,
311
+ include: { documents: true, summaries: true },
312
+ limit,
313
+ q: searchQuery,
314
+ searchMode: 'hybrid',
315
+ },
316
+ requestOptions(runtime.config.timeoutMs)
317
+ );
318
+
319
+ const results = response.results as AiMemorySearchResultLike[];
320
+
321
+ return {
322
+ ok: true,
323
+ value: results
324
+ .map((result) => normalizeSearchResult(result))
325
+ .filter((result) => result.value.trim()),
326
+ };
327
+ } catch (error) {
328
+ return fail<AiMemorySearchResult[]>(error, [], runtime.config.failOpen);
329
+ }
330
+ }
331
+
332
+ export async function listAiMemories({
333
+ category,
334
+ ignoreSettings,
335
+ limit = 100,
336
+ scope,
337
+ }: {
338
+ category?: string | null;
339
+ ignoreSettings?: boolean;
340
+ limit?: number;
341
+ scope: AiMemoryScope | null;
342
+ }): Promise<AiMemoryResult<AiMemoryDocument[]>> {
343
+ const runtime = await ensureMemoryRuntime<AiMemoryDocument[]>({
344
+ fallback: [],
345
+ ignoreSettings,
346
+ scope,
347
+ });
348
+ if (!runtime.ready) return runtime.skip;
349
+
350
+ try {
351
+ const filters = category
352
+ ? {
353
+ AND: [
354
+ buildProductFilter(runtime.scope.product),
355
+ categoryFilter(category),
356
+ ],
357
+ }
358
+ : buildProductFilter(runtime.scope.product);
359
+ const response = await runtime.client.listDocuments(
360
+ {
361
+ containerTags: [runtime.scope.containerTag],
362
+ filters: filters as never,
363
+ includeContent: true,
364
+ limit,
365
+ order: 'desc',
366
+ sort: 'updatedAt',
367
+ },
368
+ requestOptions(runtime.config.timeoutMs)
369
+ );
370
+
371
+ const documents = response.memories as AiMemoryDocumentLike[];
372
+
373
+ return {
374
+ ok: true,
375
+ value: documents.map((doc) => normalizeDocument(doc)),
376
+ };
377
+ } catch (error) {
378
+ return fail<AiMemoryDocument[]>(error, [], runtime.config.failOpen);
379
+ }
380
+ }
381
+
382
+ export async function forgetAiMemory({
383
+ ignoreSettings,
384
+ key,
385
+ memoryId,
386
+ reason = 'User requested memory deletion from Tuturuuu.',
387
+ scope,
388
+ }: {
389
+ ignoreSettings?: boolean;
390
+ key?: string | null;
391
+ memoryId?: string | null;
392
+ reason?: string;
393
+ scope: AiMemoryScope | null;
394
+ }): Promise<AiMemoryResult<{ forgotten: boolean; id: string } | null>> {
395
+ const runtime = await ensureMemoryRuntime<{
396
+ forgotten: boolean;
397
+ id: string;
398
+ } | null>({
399
+ fallback: null,
400
+ ignoreSettings,
401
+ scope,
402
+ });
403
+ if (!runtime.ready) return runtime.skip;
404
+
405
+ try {
406
+ if (memoryId) {
407
+ const response = await runtime.client.forgetMemory(
408
+ {
409
+ containerTag: runtime.scope.containerTag,
410
+ id: memoryId,
411
+ reason,
412
+ },
413
+ requestOptions(runtime.config.timeoutMs)
414
+ );
415
+ return { ok: true, value: response };
416
+ }
417
+
418
+ if (key) {
419
+ const matches = await searchAiMemories({
420
+ ignoreSettings,
421
+ limit: 5,
422
+ query: key,
423
+ scope: runtime.scope,
424
+ });
425
+ if (!matches.ok) return matches;
426
+
427
+ const exact = (matches.value ?? []).find((match) => match.key === key);
428
+ if (!exact) {
429
+ return skipped<{ forgotten: boolean; id: string } | null>(
430
+ 'memory_not_found',
431
+ null
432
+ );
433
+ }
434
+
435
+ const response = await runtime.client.forgetMemory(
436
+ {
437
+ containerTag: runtime.scope.containerTag,
438
+ id: exact.id,
439
+ reason,
440
+ },
441
+ requestOptions(runtime.config.timeoutMs)
442
+ );
443
+ return { ok: true, value: response };
444
+ }
445
+
446
+ return { error: 'memoryId or key is required', ok: false };
447
+ } catch (error) {
448
+ return fail(error, null, runtime.config.failOpen);
449
+ }
450
+ }
451
+
452
+ export async function buildAiMemoryContext({
453
+ ignoreSettings,
454
+ includeProductFilter = true,
455
+ limit = DEFAULT_CONTEXT_LIMIT,
456
+ query,
457
+ scope,
458
+ }: {
459
+ ignoreSettings?: boolean;
460
+ includeProductFilter?: boolean;
461
+ limit?: number;
462
+ query?: string | null;
463
+ scope: AiMemoryScope | null;
464
+ }): Promise<string> {
465
+ const search = await searchAiMemories({
466
+ ignoreSettings,
467
+ includeProductFilter,
468
+ limit,
469
+ query: query ?? '',
470
+ scope,
471
+ });
472
+ if (!search.ok || !search.value?.length) return '';
473
+
474
+ const lines = search.value.map((memory) => {
475
+ const key = memory.key ? `${memory.key}: ` : '';
476
+ return `- ${key}${memory.value}`;
477
+ });
478
+
479
+ return `## User Memories\n${lines.join('\n')}`;
480
+ }