@twelvehart/supermemory-runtime 1.0.0-next.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 (156) hide show
  1. package/.env.example +57 -0
  2. package/README.md +374 -0
  3. package/dist/index.js +189 -0
  4. package/dist/mcp/index.js +1132 -0
  5. package/docker-compose.prod.yml +91 -0
  6. package/docker-compose.yml +358 -0
  7. package/drizzle/0000_dapper_the_professor.sql +159 -0
  8. package/drizzle/0001_api_keys.sql +51 -0
  9. package/drizzle/meta/0000_snapshot.json +1532 -0
  10. package/drizzle/meta/_journal.json +13 -0
  11. package/drizzle.config.ts +20 -0
  12. package/package.json +114 -0
  13. package/scripts/add-extraction-job.ts +122 -0
  14. package/scripts/benchmark-pgvector.ts +122 -0
  15. package/scripts/bootstrap.sh +209 -0
  16. package/scripts/check-runtime-pack.ts +111 -0
  17. package/scripts/claude-mcp-config.ts +336 -0
  18. package/scripts/docker-entrypoint.sh +183 -0
  19. package/scripts/doctor.ts +377 -0
  20. package/scripts/init-db.sql +33 -0
  21. package/scripts/install.sh +1110 -0
  22. package/scripts/mcp-setup.ts +271 -0
  23. package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
  24. package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
  25. package/scripts/migrations/003_create_hnsw_index.sql +94 -0
  26. package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
  27. package/scripts/migrations/005_create_chunks_table.sql +95 -0
  28. package/scripts/migrations/006_create_processing_queue.sql +45 -0
  29. package/scripts/migrations/generate_test_data.sql +42 -0
  30. package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
  31. package/scripts/migrations/run_migrations.sh +286 -0
  32. package/scripts/migrations/test_hnsw_index.sql +255 -0
  33. package/scripts/pre-commit-secrets +282 -0
  34. package/scripts/run-extraction-worker.ts +46 -0
  35. package/scripts/run-phase1-tests.sh +291 -0
  36. package/scripts/setup.ts +222 -0
  37. package/scripts/smoke-install.sh +12 -0
  38. package/scripts/test-health-endpoint.sh +328 -0
  39. package/src/api/index.ts +2 -0
  40. package/src/api/middleware/auth.ts +80 -0
  41. package/src/api/middleware/csrf.ts +308 -0
  42. package/src/api/middleware/errorHandler.ts +166 -0
  43. package/src/api/middleware/rateLimit.ts +360 -0
  44. package/src/api/middleware/validation.ts +514 -0
  45. package/src/api/routes/documents.ts +286 -0
  46. package/src/api/routes/profiles.ts +237 -0
  47. package/src/api/routes/search.ts +71 -0
  48. package/src/api/stores/index.ts +58 -0
  49. package/src/config/bootstrap-env.ts +3 -0
  50. package/src/config/env.ts +71 -0
  51. package/src/config/feature-flags.ts +25 -0
  52. package/src/config/index.ts +140 -0
  53. package/src/config/secrets.config.ts +291 -0
  54. package/src/db/client.ts +92 -0
  55. package/src/db/index.ts +73 -0
  56. package/src/db/postgres.ts +72 -0
  57. package/src/db/schema/chunks.schema.ts +31 -0
  58. package/src/db/schema/containers.schema.ts +46 -0
  59. package/src/db/schema/documents.schema.ts +49 -0
  60. package/src/db/schema/embeddings.schema.ts +32 -0
  61. package/src/db/schema/index.ts +11 -0
  62. package/src/db/schema/memories.schema.ts +72 -0
  63. package/src/db/schema/profiles.schema.ts +34 -0
  64. package/src/db/schema/queue.schema.ts +59 -0
  65. package/src/db/schema/relationships.schema.ts +42 -0
  66. package/src/db/schema.ts +223 -0
  67. package/src/db/worker-connection.ts +47 -0
  68. package/src/index.ts +235 -0
  69. package/src/mcp/CLAUDE.md +1 -0
  70. package/src/mcp/index.ts +1380 -0
  71. package/src/mcp/legacyState.ts +22 -0
  72. package/src/mcp/rateLimit.ts +358 -0
  73. package/src/mcp/resources.ts +309 -0
  74. package/src/mcp/results.ts +104 -0
  75. package/src/mcp/tools.ts +401 -0
  76. package/src/queues/config.ts +119 -0
  77. package/src/queues/index.ts +289 -0
  78. package/src/sdk/client.ts +225 -0
  79. package/src/sdk/errors.ts +266 -0
  80. package/src/sdk/http.ts +560 -0
  81. package/src/sdk/index.ts +244 -0
  82. package/src/sdk/resources/base.ts +65 -0
  83. package/src/sdk/resources/connections.ts +204 -0
  84. package/src/sdk/resources/documents.ts +163 -0
  85. package/src/sdk/resources/index.ts +10 -0
  86. package/src/sdk/resources/memories.ts +150 -0
  87. package/src/sdk/resources/search.ts +60 -0
  88. package/src/sdk/resources/settings.ts +36 -0
  89. package/src/sdk/types.ts +674 -0
  90. package/src/services/chunking/index.ts +451 -0
  91. package/src/services/chunking.service.ts +650 -0
  92. package/src/services/csrf.service.ts +252 -0
  93. package/src/services/documents.repository.ts +219 -0
  94. package/src/services/documents.service.ts +191 -0
  95. package/src/services/embedding.service.ts +404 -0
  96. package/src/services/extraction.service.ts +300 -0
  97. package/src/services/extractors/code.extractor.ts +451 -0
  98. package/src/services/extractors/index.ts +9 -0
  99. package/src/services/extractors/markdown.extractor.ts +461 -0
  100. package/src/services/extractors/pdf.extractor.ts +315 -0
  101. package/src/services/extractors/text.extractor.ts +118 -0
  102. package/src/services/extractors/url.extractor.ts +243 -0
  103. package/src/services/index.ts +235 -0
  104. package/src/services/ingestion.service.ts +177 -0
  105. package/src/services/llm/anthropic.ts +400 -0
  106. package/src/services/llm/base.ts +460 -0
  107. package/src/services/llm/contradiction-detector.service.ts +526 -0
  108. package/src/services/llm/heuristics.ts +148 -0
  109. package/src/services/llm/index.ts +309 -0
  110. package/src/services/llm/memory-classifier.service.ts +383 -0
  111. package/src/services/llm/memory-extension-detector.service.ts +523 -0
  112. package/src/services/llm/mock.ts +470 -0
  113. package/src/services/llm/openai.ts +398 -0
  114. package/src/services/llm/prompts.ts +438 -0
  115. package/src/services/llm/types.ts +373 -0
  116. package/src/services/memory.repository.ts +1769 -0
  117. package/src/services/memory.service.ts +1338 -0
  118. package/src/services/memory.types.ts +234 -0
  119. package/src/services/persistence/index.ts +295 -0
  120. package/src/services/pipeline.service.ts +509 -0
  121. package/src/services/profile.repository.ts +436 -0
  122. package/src/services/profile.service.ts +560 -0
  123. package/src/services/profile.types.ts +270 -0
  124. package/src/services/relationships/detector.ts +1128 -0
  125. package/src/services/relationships/index.ts +268 -0
  126. package/src/services/relationships/memory-integration.ts +459 -0
  127. package/src/services/relationships/strategies.ts +132 -0
  128. package/src/services/relationships/types.ts +370 -0
  129. package/src/services/search.service.ts +761 -0
  130. package/src/services/search.types.ts +220 -0
  131. package/src/services/secrets.service.ts +384 -0
  132. package/src/services/vectorstore/base.ts +327 -0
  133. package/src/services/vectorstore/index.ts +444 -0
  134. package/src/services/vectorstore/memory.ts +286 -0
  135. package/src/services/vectorstore/migration.ts +295 -0
  136. package/src/services/vectorstore/mock.ts +403 -0
  137. package/src/services/vectorstore/pgvector.ts +695 -0
  138. package/src/services/vectorstore/types.ts +247 -0
  139. package/src/startup.ts +389 -0
  140. package/src/types/api.types.ts +193 -0
  141. package/src/types/document.types.ts +103 -0
  142. package/src/types/index.ts +241 -0
  143. package/src/types/profile.base.ts +133 -0
  144. package/src/utils/errors.ts +447 -0
  145. package/src/utils/id.ts +15 -0
  146. package/src/utils/index.ts +101 -0
  147. package/src/utils/logger.ts +313 -0
  148. package/src/utils/sanitization.ts +501 -0
  149. package/src/utils/secret-validation.ts +273 -0
  150. package/src/utils/synonyms.ts +188 -0
  151. package/src/utils/validation.ts +581 -0
  152. package/src/workers/chunking.worker.ts +242 -0
  153. package/src/workers/embedding.worker.ts +358 -0
  154. package/src/workers/extraction.worker.ts +346 -0
  155. package/src/workers/indexing.worker.ts +505 -0
  156. package/tsconfig.json +38 -0
@@ -0,0 +1,104 @@
1
+ import { ErrorCode as McpProtocolErrorCode, McpError, type CallToolResult } from '@modelcontextprotocol/sdk/types.js'
2
+ import { ZodError } from 'zod'
3
+ import { AppError, ValidationError, ErrorCode as AppErrorCode } from '../utils/errors.js'
4
+
5
+ export interface McpEnvelopeError {
6
+ code: string
7
+ message: string
8
+ details?: unknown
9
+ retriable?: boolean
10
+ }
11
+
12
+ export interface McpResultEnvelope<T> extends Record<string, unknown> {
13
+ ok: boolean
14
+ data: T | null
15
+ warnings: string[]
16
+ errors: McpEnvelopeError[]
17
+ meta: {
18
+ tool: string
19
+ timestamp: string
20
+ partial?: boolean
21
+ }
22
+ }
23
+
24
+ function extractHumanMessage<T>(toolName: string, data: T | null, ok: boolean): string {
25
+ if (data && typeof data === 'object' && data !== null && 'message' in data && typeof data.message === 'string') {
26
+ return data.message
27
+ }
28
+
29
+ return ok ? `${toolName} completed successfully.` : `${toolName} completed with errors.`
30
+ }
31
+
32
+ function formatValidationMessage(error: ValidationError): string {
33
+ return `${error.message} | fieldErrors=${JSON.stringify(error.fieldErrors)}`
34
+ }
35
+
36
+ export function createMcpEnvelopeError(
37
+ code: string,
38
+ message: string,
39
+ details?: unknown,
40
+ retriable?: boolean
41
+ ): McpEnvelopeError {
42
+ return { code, message, details, retriable }
43
+ }
44
+
45
+ export function createToolResponse<T>(options: {
46
+ data: T | null
47
+ errors?: McpEnvelopeError[]
48
+ ok: boolean
49
+ tool: string
50
+ warnings?: string[]
51
+ partial?: boolean
52
+ }): CallToolResult {
53
+ const warnings = options.warnings ?? []
54
+ const errors = options.errors ?? []
55
+ const envelope: McpResultEnvelope<T> = {
56
+ ok: options.ok,
57
+ data: options.data,
58
+ warnings,
59
+ errors,
60
+ meta: {
61
+ tool: options.tool,
62
+ timestamp: new Date().toISOString(),
63
+ ...(options.partial ? { partial: true } : {}),
64
+ },
65
+ }
66
+
67
+ const text = `${extractHumanMessage(options.tool, options.data, options.ok)}\n\n${JSON.stringify(envelope, null, 2)}`
68
+
69
+ return {
70
+ content: [{ type: 'text', text }],
71
+ structuredContent: envelope,
72
+ ...(options.ok && errors.length === 0 ? {} : { isError: true }),
73
+ }
74
+ }
75
+
76
+ export function mapErrorToMcpError(error: unknown): McpError {
77
+ if (error instanceof McpError) {
78
+ return error
79
+ }
80
+
81
+ if (error instanceof ZodError) {
82
+ const validationError = ValidationError.fromZodError(error)
83
+ return new McpError(McpProtocolErrorCode.InvalidParams, formatValidationMessage(validationError))
84
+ }
85
+
86
+ if (error instanceof ValidationError) {
87
+ return new McpError(McpProtocolErrorCode.InvalidParams, formatValidationMessage(error))
88
+ }
89
+
90
+ if (error instanceof AppError) {
91
+ if (error.code === AppErrorCode.NOT_FOUND || error.statusCode === 404) {
92
+ return new McpError(McpProtocolErrorCode.InvalidRequest, error.message)
93
+ }
94
+
95
+ if (error.statusCode >= 400 && error.statusCode < 500) {
96
+ return new McpError(McpProtocolErrorCode.InvalidParams, error.message)
97
+ }
98
+
99
+ return new McpError(McpProtocolErrorCode.InternalError, error.message)
100
+ }
101
+
102
+ const message = error instanceof Error ? error.message : 'Unknown error'
103
+ return new McpError(McpProtocolErrorCode.InternalError, message)
104
+ }
@@ -0,0 +1,401 @@
1
+ /**
2
+ * MCP Tool Definitions for Supermemory
3
+ */
4
+
5
+ import { z } from 'zod'
6
+
7
+ const MAX_CONTENT_CHARS = 50000
8
+ const MAX_QUERY_CHARS = 10000
9
+ const MAX_CONTAINER_TAG_CHARS = 100
10
+ const MAX_TITLE_CHARS = 500
11
+ const MAX_FACT_CHARS = 5000
12
+ const MAX_METADATA_BYTES = 10240
13
+
14
+ const boundedMetadataSchema = z
15
+ .record(z.unknown())
16
+ .optional()
17
+ .refine(
18
+ (metadata) => {
19
+ if (!metadata) return true
20
+ try {
21
+ const jsonSize = new TextEncoder().encode(JSON.stringify(metadata)).length
22
+ return jsonSize <= MAX_METADATA_BYTES
23
+ } catch {
24
+ return false
25
+ }
26
+ },
27
+ { message: `Metadata must be at most ${MAX_METADATA_BYTES} bytes (10KB)` }
28
+ )
29
+
30
+ export const AddContentInputSchema = z
31
+ .object({
32
+ content: z
33
+ .string()
34
+ .min(1, 'Content is required')
35
+ .max(MAX_CONTENT_CHARS, `Content must be at most ${MAX_CONTENT_CHARS} characters`),
36
+ customId: z
37
+ .string()
38
+ .min(1, 'customId cannot be empty')
39
+ .max(255, 'customId must be at most 255 characters')
40
+ .optional(),
41
+ idempotencyKey: z
42
+ .string()
43
+ .min(1, 'idempotencyKey cannot be empty')
44
+ .max(255, 'idempotencyKey must be at most 255 characters')
45
+ .optional(),
46
+ containerTag: z
47
+ .string()
48
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
49
+ .regex(/^[a-zA-Z0-9_-]+$/, 'Container tag can only contain alphanumeric characters, underscores, and hyphens')
50
+ .optional()
51
+ .describe('Container/namespace for organizing memories'),
52
+ metadata: boundedMetadataSchema.describe('Additional metadata to attach (max 10KB)'),
53
+ sourceUrl: z
54
+ .string()
55
+ .url()
56
+ .refine(
57
+ (url) => {
58
+ try {
59
+ const parsed = new URL(url)
60
+ return ['http:', 'https:'].includes(parsed.protocol)
61
+ } catch {
62
+ return false
63
+ }
64
+ },
65
+ { message: 'URL must use http or https protocol' }
66
+ )
67
+ .optional(),
68
+ title: z.string().max(MAX_TITLE_CHARS, `Title must be at most ${MAX_TITLE_CHARS} characters`).optional(),
69
+ upsert: z.boolean().optional().default(false),
70
+ })
71
+ .superRefine((input, ctx) => {
72
+ if (input.customId && input.idempotencyKey && input.customId !== input.idempotencyKey) {
73
+ ctx.addIssue({
74
+ code: z.ZodIssueCode.custom,
75
+ message: 'customId and idempotencyKey must match when both are provided',
76
+ path: ['idempotencyKey'],
77
+ })
78
+ }
79
+ })
80
+
81
+ export const SearchInputSchema = z.object({
82
+ query: z
83
+ .string()
84
+ .min(1, 'Query is required')
85
+ .max(MAX_QUERY_CHARS, `Query must be at most ${MAX_QUERY_CHARS} characters`),
86
+ containerTag: z
87
+ .string()
88
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
89
+ .optional(),
90
+ mode: z.enum(['vector', 'memory', 'hybrid']).optional().default('hybrid'),
91
+ limit: z.number().min(1).max(100).optional().default(10),
92
+ threshold: z.number().min(0).max(1).optional().default(0.5),
93
+ rerank: z.boolean().optional().default(false),
94
+ includeMetadata: z.boolean().optional().default(true),
95
+ })
96
+
97
+ export const ProfileInputSchema = z.object({
98
+ containerTag: z
99
+ .string()
100
+ .min(1, 'Container tag is required')
101
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`),
102
+ action: z.enum(['get', 'update', 'ingest']).optional().default('get'),
103
+ content: z.string().max(MAX_CONTENT_CHARS, `Content must be at most ${MAX_CONTENT_CHARS} characters`).optional(),
104
+ facts: z
105
+ .array(
106
+ z.object({
107
+ content: z
108
+ .string()
109
+ .min(1, 'Fact content is required')
110
+ .max(MAX_FACT_CHARS, `Fact must be at most ${MAX_FACT_CHARS} characters`),
111
+ type: z.enum(['static', 'dynamic']).optional(),
112
+ category: z.string().max(100, 'Category must be at most 100 characters').optional(),
113
+ })
114
+ )
115
+ .max(100, 'Cannot add more than 100 facts at once')
116
+ .optional(),
117
+ })
118
+
119
+ export const ListDocumentsInputSchema = z.object({
120
+ containerTag: z
121
+ .string()
122
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
123
+ .optional(),
124
+ limit: z.number().min(1).max(100).optional().default(20),
125
+ offset: z.number().min(0).optional().default(0),
126
+ contentType: z.enum(['note', 'url', 'pdf', 'image', 'tweet', 'document']).optional(),
127
+ sortBy: z.enum(['createdAt', 'updatedAt', 'title']).optional().default('createdAt'),
128
+ sortOrder: z.enum(['asc', 'desc']).optional().default('desc'),
129
+ })
130
+
131
+ export const DeleteContentInputSchema = z.object({
132
+ id: z.string().max(255, 'ID must be at most 255 characters').optional(),
133
+ containerTag: z
134
+ .string()
135
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
136
+ .optional(),
137
+ confirm: z.boolean(),
138
+ })
139
+
140
+ export const RememberInputSchema = z.object({
141
+ fact: z.string().min(1, 'Fact is required').max(MAX_FACT_CHARS, `Fact must be at most ${MAX_FACT_CHARS} characters`),
142
+ containerTag: z
143
+ .string()
144
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
145
+ .optional()
146
+ .default('default'),
147
+ type: z.enum(['static', 'dynamic']).optional().default('static'),
148
+ category: z
149
+ .enum(['identity', 'preference', 'skill', 'background', 'relationship', 'project', 'goal', 'context', 'other'])
150
+ .optional(),
151
+ expirationHours: z.number().min(1).max(8760).optional(),
152
+ })
153
+
154
+ export const RecallInputSchema = z.object({
155
+ query: z
156
+ .string()
157
+ .min(1, 'Query is required')
158
+ .max(MAX_QUERY_CHARS, `Query must be at most ${MAX_QUERY_CHARS} characters`),
159
+ containerTag: z
160
+ .string()
161
+ .max(MAX_CONTAINER_TAG_CHARS, `Container tag must be at most ${MAX_CONTAINER_TAG_CHARS} characters`)
162
+ .optional()
163
+ .default('default'),
164
+ includeStatic: z.boolean().optional().default(true),
165
+ includeDynamic: z.boolean().optional().default(true),
166
+ limit: z.number().min(1).max(50).optional().default(10),
167
+ })
168
+
169
+ export interface ToolDefinition {
170
+ name: string
171
+ description: string
172
+ inputSchema: {
173
+ type: 'object'
174
+ properties: Record<string, unknown>
175
+ required?: string[]
176
+ }
177
+ }
178
+
179
+ export const TOOL_DEFINITIONS: ToolDefinition[] = [
180
+ {
181
+ name: 'supermemory_add',
182
+ description: 'Add content and extract/index memories.',
183
+ inputSchema: {
184
+ type: 'object',
185
+ properties: {
186
+ content: { type: 'string' },
187
+ customId: { type: 'string' },
188
+ idempotencyKey: { type: 'string' },
189
+ containerTag: { type: 'string' },
190
+ metadata: { type: 'object', additionalProperties: true },
191
+ sourceUrl: { type: 'string' },
192
+ title: { type: 'string' },
193
+ upsert: { type: 'boolean' },
194
+ },
195
+ required: ['content'],
196
+ },
197
+ },
198
+ {
199
+ name: 'supermemory_search',
200
+ description: 'Semantic/memory/hybrid search across stored memories.',
201
+ inputSchema: {
202
+ type: 'object',
203
+ properties: {
204
+ query: { type: 'string' },
205
+ containerTag: { type: 'string' },
206
+ mode: { type: 'string', enum: ['vector', 'memory', 'hybrid'] },
207
+ limit: { type: 'number' },
208
+ threshold: { type: 'number' },
209
+ rerank: { type: 'boolean' },
210
+ },
211
+ required: ['query'],
212
+ },
213
+ },
214
+ {
215
+ name: 'supermemory_profile',
216
+ description: 'Get/ingest/update profile facts for a container.',
217
+ inputSchema: {
218
+ type: 'object',
219
+ properties: {
220
+ containerTag: { type: 'string' },
221
+ action: { type: 'string', enum: ['get', 'update', 'ingest'] },
222
+ content: { type: 'string' },
223
+ facts: {
224
+ type: 'array',
225
+ items: {
226
+ type: 'object',
227
+ properties: {
228
+ content: { type: 'string' },
229
+ type: { type: 'string', enum: ['static', 'dynamic'] },
230
+ category: { type: 'string' },
231
+ },
232
+ required: ['content'],
233
+ },
234
+ },
235
+ },
236
+ required: ['containerTag'],
237
+ },
238
+ },
239
+ {
240
+ name: 'supermemory_list',
241
+ description: 'List stored documents.',
242
+ inputSchema: {
243
+ type: 'object',
244
+ properties: {
245
+ containerTag: { type: 'string' },
246
+ limit: { type: 'number' },
247
+ offset: { type: 'number' },
248
+ contentType: { type: 'string', enum: ['note', 'url', 'pdf', 'image', 'tweet', 'document'] },
249
+ sortBy: { type: 'string', enum: ['createdAt', 'updatedAt', 'title'] },
250
+ sortOrder: { type: 'string', enum: ['asc', 'desc'] },
251
+ },
252
+ },
253
+ },
254
+ {
255
+ name: 'supermemory_delete',
256
+ description: 'Delete content by ID or containerTag.',
257
+ inputSchema: {
258
+ type: 'object',
259
+ properties: {
260
+ id: { type: 'string' },
261
+ containerTag: { type: 'string' },
262
+ confirm: { type: 'boolean' },
263
+ },
264
+ required: ['confirm'],
265
+ },
266
+ },
267
+ {
268
+ name: 'supermemory_remember',
269
+ description: 'Store a specific fact for future recall.',
270
+ inputSchema: {
271
+ type: 'object',
272
+ properties: {
273
+ fact: { type: 'string' },
274
+ containerTag: { type: 'string' },
275
+ type: { type: 'string', enum: ['static', 'dynamic'] },
276
+ category: {
277
+ type: 'string',
278
+ enum: [
279
+ 'identity',
280
+ 'preference',
281
+ 'skill',
282
+ 'background',
283
+ 'relationship',
284
+ 'project',
285
+ 'goal',
286
+ 'context',
287
+ 'other',
288
+ ],
289
+ },
290
+ expirationHours: { type: 'number' },
291
+ },
292
+ required: ['fact'],
293
+ },
294
+ },
295
+ {
296
+ name: 'supermemory_recall',
297
+ description: 'Recall stored facts matching a query.',
298
+ inputSchema: {
299
+ type: 'object',
300
+ properties: {
301
+ query: { type: 'string' },
302
+ containerTag: { type: 'string' },
303
+ includeStatic: { type: 'boolean' },
304
+ includeDynamic: { type: 'boolean' },
305
+ limit: { type: 'number' },
306
+ },
307
+ required: ['query'],
308
+ },
309
+ },
310
+ ]
311
+
312
+ export interface AddContentResult {
313
+ success: boolean
314
+ documentId?: string
315
+ customId?: string
316
+ created?: boolean
317
+ reused?: boolean
318
+ updated?: boolean
319
+ memoriesExtracted?: number
320
+ message: string
321
+ errors?: string[]
322
+ }
323
+
324
+ export interface SearchResultItem {
325
+ id: string
326
+ content: string
327
+ similarity: number
328
+ containerTag?: string
329
+ metadata?: Record<string, unknown>
330
+ createdAt?: string
331
+ }
332
+
333
+ export interface SearchResult {
334
+ results: SearchResultItem[]
335
+ totalCount: number
336
+ query: string
337
+ searchTimeMs: number
338
+ }
339
+
340
+ export interface ProfileResult {
341
+ containerTag: string
342
+ staticFacts: Array<{
343
+ id: string
344
+ content: string
345
+ category?: string
346
+ confidence: number
347
+ }>
348
+ dynamicFacts: Array<{
349
+ id: string
350
+ content: string
351
+ category?: string
352
+ expiresAt?: string
353
+ }>
354
+ lastUpdated: string
355
+ }
356
+
357
+ export interface ListResult {
358
+ documents: Array<{
359
+ id: string
360
+ title?: string
361
+ contentPreview: string
362
+ contentType: string
363
+ containerTag?: string
364
+ createdAt: string
365
+ updatedAt: string
366
+ }>
367
+ total: number
368
+ limit: number
369
+ offset: number
370
+ hasMore: boolean
371
+ }
372
+
373
+ export interface DeleteResult {
374
+ success: boolean
375
+ documentsDeleted: number
376
+ memoriesDeleted: number
377
+ vectorsDeleted: number
378
+ profileFactsDeleted: number
379
+ deletedCount: number
380
+ message: string
381
+ errors?: string[]
382
+ }
383
+
384
+ export interface RememberResult {
385
+ success: boolean
386
+ factId: string
387
+ message: string
388
+ }
389
+
390
+ export interface RecallResult {
391
+ facts: Array<{
392
+ id: string
393
+ content: string
394
+ type: 'static' | 'dynamic'
395
+ category?: string
396
+ confidence: number
397
+ createdAt: string
398
+ }>
399
+ query: string
400
+ totalFound: number
401
+ }
@@ -0,0 +1,119 @@
1
+ /**
2
+ * BullMQ Queue Configuration
3
+ *
4
+ * Configures Redis connection, queue options, retry logic, and concurrency settings
5
+ * for the job queue system.
6
+ */
7
+
8
+ import { QueueOptions, WorkerOptions } from 'bullmq'
9
+
10
+ /**
11
+ * Redis connection configuration with health checks and reconnection logic
12
+ */
13
+ export const redisConfig = {
14
+ connection: {
15
+ host: process.env.REDIS_HOST || 'localhost',
16
+ port: parseInt(process.env.REDIS_PORT || '6379', 10),
17
+ maxRetriesPerRequest: null, // Required for BullMQ
18
+ enableReadyCheck: true,
19
+ enableOfflineQueue: true,
20
+ retryStrategy: (times: number) => {
21
+ // Exponential backoff: 1s, 2s, 4s, 8s, max 30s
22
+ const delay = Math.min(times * 1000, 30000)
23
+ console.log(`[Redis] Reconnection attempt ${times}, waiting ${delay}ms`)
24
+ return delay
25
+ },
26
+ reconnectOnError: (err: Error) => {
27
+ const targetErrors = ['READONLY', 'ECONNREFUSED', 'ETIMEDOUT']
28
+ if (targetErrors.some((targetError) => err.message.includes(targetError))) {
29
+ console.log(`[Redis] Reconnecting due to error: ${err.message}`)
30
+ return true
31
+ }
32
+ return false
33
+ },
34
+ },
35
+ }
36
+
37
+ /**
38
+ * Default queue options with retry logic and dead letter queue support
39
+ */
40
+ export const defaultQueueOptions: QueueOptions = {
41
+ connection: redisConfig.connection,
42
+ defaultJobOptions: {
43
+ attempts: 3, // Maximum retry attempts
44
+ backoff: {
45
+ type: 'exponential',
46
+ delay: 1000, // Initial delay: 1 second
47
+ },
48
+ removeOnComplete: {
49
+ count: 100, // Keep last 100 completed jobs
50
+ age: 24 * 3600, // Keep for 24 hours
51
+ },
52
+ removeOnFail: {
53
+ count: 500, // Keep last 500 failed jobs for debugging
54
+ age: 7 * 24 * 3600, // Keep for 7 days
55
+ },
56
+ },
57
+ }
58
+
59
+ /**
60
+ * Queue-specific concurrency settings from environment variables
61
+ */
62
+ export const concurrencySettings = {
63
+ extraction: parseInt(process.env.BULLMQ_CONCURRENCY_EXTRACTION || '5', 10),
64
+ chunking: parseInt(process.env.BULLMQ_CONCURRENCY_CHUNKING || '3', 10),
65
+ embedding: parseInt(process.env.BULLMQ_CONCURRENCY_EMBEDDING || '2', 10),
66
+ indexing: parseInt(process.env.BULLMQ_CONCURRENCY_INDEXING || '1', 10),
67
+ }
68
+
69
+ /**
70
+ * Worker options factory
71
+ * Creates worker configuration with concurrency settings
72
+ */
73
+ export function createWorkerOptions(queueName: keyof typeof concurrencySettings): WorkerOptions {
74
+ return {
75
+ connection: redisConfig.connection,
76
+ concurrency: concurrencySettings[queueName],
77
+ autorun: true,
78
+ removeOnComplete: { count: 100 },
79
+ removeOnFail: { count: 500 },
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Dead letter queue configuration
85
+ * Failed jobs after max retries are moved here for manual inspection
86
+ */
87
+ export const deadLetterQueueOptions: QueueOptions = {
88
+ ...defaultQueueOptions,
89
+ defaultJobOptions: {
90
+ attempts: 1, // No retries for dead letter queue
91
+ removeOnComplete: {
92
+ count: 1000,
93
+ age: 30 * 24 * 3600, // Keep for 30 days
94
+ },
95
+ removeOnFail: false, // Never remove failed jobs from DLQ
96
+ },
97
+ }
98
+
99
+ /**
100
+ * Priority levels for job scheduling
101
+ * Higher number = higher priority (executed first)
102
+ */
103
+ export enum JobPriority {
104
+ LOW = 1,
105
+ NORMAL = 5,
106
+ HIGH = 8,
107
+ CRITICAL = 10,
108
+ }
109
+
110
+ /**
111
+ * Job progress tracking interface
112
+ */
113
+ export interface JobProgress {
114
+ percentage: number // 0-100
115
+ stage?: string
116
+ message?: string
117
+ processedItems?: number
118
+ totalItems?: number
119
+ }