@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.
- package/.env.example +57 -0
- package/README.md +374 -0
- package/dist/index.js +189 -0
- package/dist/mcp/index.js +1132 -0
- package/docker-compose.prod.yml +91 -0
- package/docker-compose.yml +358 -0
- package/drizzle/0000_dapper_the_professor.sql +159 -0
- package/drizzle/0001_api_keys.sql +51 -0
- package/drizzle/meta/0000_snapshot.json +1532 -0
- package/drizzle/meta/_journal.json +13 -0
- package/drizzle.config.ts +20 -0
- package/package.json +114 -0
- package/scripts/add-extraction-job.ts +122 -0
- package/scripts/benchmark-pgvector.ts +122 -0
- package/scripts/bootstrap.sh +209 -0
- package/scripts/check-runtime-pack.ts +111 -0
- package/scripts/claude-mcp-config.ts +336 -0
- package/scripts/docker-entrypoint.sh +183 -0
- package/scripts/doctor.ts +377 -0
- package/scripts/init-db.sql +33 -0
- package/scripts/install.sh +1110 -0
- package/scripts/mcp-setup.ts +271 -0
- package/scripts/migrations/001_create_pgvector_extension.sql +31 -0
- package/scripts/migrations/002_create_memory_embeddings_table.sql +75 -0
- package/scripts/migrations/003_create_hnsw_index.sql +94 -0
- package/scripts/migrations/004_create_memory_embeddings_standalone.sql +70 -0
- package/scripts/migrations/005_create_chunks_table.sql +95 -0
- package/scripts/migrations/006_create_processing_queue.sql +45 -0
- package/scripts/migrations/generate_test_data.sql +42 -0
- package/scripts/migrations/phase1_comprehensive_test.sql +204 -0
- package/scripts/migrations/run_migrations.sh +286 -0
- package/scripts/migrations/test_hnsw_index.sql +255 -0
- package/scripts/pre-commit-secrets +282 -0
- package/scripts/run-extraction-worker.ts +46 -0
- package/scripts/run-phase1-tests.sh +291 -0
- package/scripts/setup.ts +222 -0
- package/scripts/smoke-install.sh +12 -0
- package/scripts/test-health-endpoint.sh +328 -0
- package/src/api/index.ts +2 -0
- package/src/api/middleware/auth.ts +80 -0
- package/src/api/middleware/csrf.ts +308 -0
- package/src/api/middleware/errorHandler.ts +166 -0
- package/src/api/middleware/rateLimit.ts +360 -0
- package/src/api/middleware/validation.ts +514 -0
- package/src/api/routes/documents.ts +286 -0
- package/src/api/routes/profiles.ts +237 -0
- package/src/api/routes/search.ts +71 -0
- package/src/api/stores/index.ts +58 -0
- package/src/config/bootstrap-env.ts +3 -0
- package/src/config/env.ts +71 -0
- package/src/config/feature-flags.ts +25 -0
- package/src/config/index.ts +140 -0
- package/src/config/secrets.config.ts +291 -0
- package/src/db/client.ts +92 -0
- package/src/db/index.ts +73 -0
- package/src/db/postgres.ts +72 -0
- package/src/db/schema/chunks.schema.ts +31 -0
- package/src/db/schema/containers.schema.ts +46 -0
- package/src/db/schema/documents.schema.ts +49 -0
- package/src/db/schema/embeddings.schema.ts +32 -0
- package/src/db/schema/index.ts +11 -0
- package/src/db/schema/memories.schema.ts +72 -0
- package/src/db/schema/profiles.schema.ts +34 -0
- package/src/db/schema/queue.schema.ts +59 -0
- package/src/db/schema/relationships.schema.ts +42 -0
- package/src/db/schema.ts +223 -0
- package/src/db/worker-connection.ts +47 -0
- package/src/index.ts +235 -0
- package/src/mcp/CLAUDE.md +1 -0
- package/src/mcp/index.ts +1380 -0
- package/src/mcp/legacyState.ts +22 -0
- package/src/mcp/rateLimit.ts +358 -0
- package/src/mcp/resources.ts +309 -0
- package/src/mcp/results.ts +104 -0
- package/src/mcp/tools.ts +401 -0
- package/src/queues/config.ts +119 -0
- package/src/queues/index.ts +289 -0
- package/src/sdk/client.ts +225 -0
- package/src/sdk/errors.ts +266 -0
- package/src/sdk/http.ts +560 -0
- package/src/sdk/index.ts +244 -0
- package/src/sdk/resources/base.ts +65 -0
- package/src/sdk/resources/connections.ts +204 -0
- package/src/sdk/resources/documents.ts +163 -0
- package/src/sdk/resources/index.ts +10 -0
- package/src/sdk/resources/memories.ts +150 -0
- package/src/sdk/resources/search.ts +60 -0
- package/src/sdk/resources/settings.ts +36 -0
- package/src/sdk/types.ts +674 -0
- package/src/services/chunking/index.ts +451 -0
- package/src/services/chunking.service.ts +650 -0
- package/src/services/csrf.service.ts +252 -0
- package/src/services/documents.repository.ts +219 -0
- package/src/services/documents.service.ts +191 -0
- package/src/services/embedding.service.ts +404 -0
- package/src/services/extraction.service.ts +300 -0
- package/src/services/extractors/code.extractor.ts +451 -0
- package/src/services/extractors/index.ts +9 -0
- package/src/services/extractors/markdown.extractor.ts +461 -0
- package/src/services/extractors/pdf.extractor.ts +315 -0
- package/src/services/extractors/text.extractor.ts +118 -0
- package/src/services/extractors/url.extractor.ts +243 -0
- package/src/services/index.ts +235 -0
- package/src/services/ingestion.service.ts +177 -0
- package/src/services/llm/anthropic.ts +400 -0
- package/src/services/llm/base.ts +460 -0
- package/src/services/llm/contradiction-detector.service.ts +526 -0
- package/src/services/llm/heuristics.ts +148 -0
- package/src/services/llm/index.ts +309 -0
- package/src/services/llm/memory-classifier.service.ts +383 -0
- package/src/services/llm/memory-extension-detector.service.ts +523 -0
- package/src/services/llm/mock.ts +470 -0
- package/src/services/llm/openai.ts +398 -0
- package/src/services/llm/prompts.ts +438 -0
- package/src/services/llm/types.ts +373 -0
- package/src/services/memory.repository.ts +1769 -0
- package/src/services/memory.service.ts +1338 -0
- package/src/services/memory.types.ts +234 -0
- package/src/services/persistence/index.ts +295 -0
- package/src/services/pipeline.service.ts +509 -0
- package/src/services/profile.repository.ts +436 -0
- package/src/services/profile.service.ts +560 -0
- package/src/services/profile.types.ts +270 -0
- package/src/services/relationships/detector.ts +1128 -0
- package/src/services/relationships/index.ts +268 -0
- package/src/services/relationships/memory-integration.ts +459 -0
- package/src/services/relationships/strategies.ts +132 -0
- package/src/services/relationships/types.ts +370 -0
- package/src/services/search.service.ts +761 -0
- package/src/services/search.types.ts +220 -0
- package/src/services/secrets.service.ts +384 -0
- package/src/services/vectorstore/base.ts +327 -0
- package/src/services/vectorstore/index.ts +444 -0
- package/src/services/vectorstore/memory.ts +286 -0
- package/src/services/vectorstore/migration.ts +295 -0
- package/src/services/vectorstore/mock.ts +403 -0
- package/src/services/vectorstore/pgvector.ts +695 -0
- package/src/services/vectorstore/types.ts +247 -0
- package/src/startup.ts +389 -0
- package/src/types/api.types.ts +193 -0
- package/src/types/document.types.ts +103 -0
- package/src/types/index.ts +241 -0
- package/src/types/profile.base.ts +133 -0
- package/src/utils/errors.ts +447 -0
- package/src/utils/id.ts +15 -0
- package/src/utils/index.ts +101 -0
- package/src/utils/logger.ts +313 -0
- package/src/utils/sanitization.ts +501 -0
- package/src/utils/secret-validation.ts +273 -0
- package/src/utils/synonyms.ts +188 -0
- package/src/utils/validation.ts +581 -0
- package/src/workers/chunking.worker.ts +242 -0
- package/src/workers/embedding.worker.ts +358 -0
- package/src/workers/extraction.worker.ts +346 -0
- package/src/workers/indexing.worker.ts +505 -0
- 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
|
+
}
|
package/src/mcp/tools.ts
ADDED
|
@@ -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
|
+
}
|