@smallironman/mcp-memory-keeper 0.12.2-fork1
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/CHANGELOG.md +542 -0
- package/LICENSE +21 -0
- package/README.md +1281 -0
- package/bin/mcp-memory-keeper +54 -0
- package/dist/__tests__/e2e/issue33-reproduce.test.js +234 -0
- package/dist/__tests__/e2e/server-e2e.test.js +341 -0
- package/dist/__tests__/helpers/database-test-helper.js +160 -0
- package/dist/__tests__/helpers/test-server.js +92 -0
- package/dist/__tests__/integration/advanced-features.test.js +614 -0
- package/dist/__tests__/integration/backward-compatibility.test.js +245 -0
- package/dist/__tests__/integration/batchOperationsE2E.test.js +396 -0
- package/dist/__tests__/integration/batchOperationsHandler.test.js +1230 -0
- package/dist/__tests__/integration/channelManagementHandler.test.js +1291 -0
- package/dist/__tests__/integration/channels.test.js +376 -0
- package/dist/__tests__/integration/checkpoint.test.js +251 -0
- package/dist/__tests__/integration/concurrent-access.test.js +190 -0
- package/dist/__tests__/integration/context-operations.test.js +243 -0
- package/dist/__tests__/integration/contextDiff.test.js +852 -0
- package/dist/__tests__/integration/contextDiffHandler.test.js +976 -0
- package/dist/__tests__/integration/contextExportHandler.test.js +510 -0
- package/dist/__tests__/integration/contextGetPaginationDefaults.test.js +298 -0
- package/dist/__tests__/integration/contextReassignChannelHandler.test.js +908 -0
- package/dist/__tests__/integration/contextRelationshipsHandler.test.js +1151 -0
- package/dist/__tests__/integration/contextSearch.test.js +1054 -0
- package/dist/__tests__/integration/contextSearchHandler.test.js +552 -0
- package/dist/__tests__/integration/contextWatchActual.test.js +165 -0
- package/dist/__tests__/integration/contextWatchHandler.test.js +1500 -0
- package/dist/__tests__/integration/database-initialization.test.js +134 -0
- package/dist/__tests__/integration/enhanced-context-operations.test.js +1082 -0
- package/dist/__tests__/integration/enhancedContextGetHandler.test.js +915 -0
- package/dist/__tests__/integration/enhancedContextTimelineHandler.test.js +716 -0
- package/dist/__tests__/integration/error-cases.test.js +411 -0
- package/dist/__tests__/integration/export-import.test.js +367 -0
- package/dist/__tests__/integration/feature-flags.test.js +542 -0
- package/dist/__tests__/integration/file-operations.test.js +264 -0
- package/dist/__tests__/integration/filterBySessionId.test.js +251 -0
- package/dist/__tests__/integration/git-integration.test.js +241 -0
- package/dist/__tests__/integration/index-tools.test.js +496 -0
- package/dist/__tests__/integration/issue11-actual-bug-demo.test.js +304 -0
- package/dist/__tests__/integration/issue11-search-filters-bug.test.js +561 -0
- package/dist/__tests__/integration/issue12-checkpoint-restore-behavior.test.js +621 -0
- package/dist/__tests__/integration/issue13-key-validation.test.js +433 -0
- package/dist/__tests__/integration/issue24-final-fix.test.js +241 -0
- package/dist/__tests__/integration/issue24-fix-validation.test.js +158 -0
- package/dist/__tests__/integration/issue24-reproduce.test.js +225 -0
- package/dist/__tests__/integration/issue24-token-limit.test.js +199 -0
- package/dist/__tests__/integration/issue33-array-items-schema.test.js +165 -0
- package/dist/__tests__/integration/knowledge-graph.test.js +338 -0
- package/dist/__tests__/integration/migrations.test.js +528 -0
- package/dist/__tests__/integration/multi-agent.test.js +546 -0
- package/dist/__tests__/integration/pagination-critical-fix.test.js +296 -0
- package/dist/__tests__/integration/paginationDefaultsHandler.test.js +600 -0
- package/dist/__tests__/integration/project-directory.test.js +291 -0
- package/dist/__tests__/integration/resource-cleanup.test.js +149 -0
- package/dist/__tests__/integration/retention.test.js +513 -0
- package/dist/__tests__/integration/search.test.js +333 -0
- package/dist/__tests__/integration/semantic-search.test.js +266 -0
- package/dist/__tests__/integration/server-initialization.test.js +305 -0
- package/dist/__tests__/integration/session-management.test.js +219 -0
- package/dist/__tests__/integration/simplified-sharing.test.js +346 -0
- package/dist/__tests__/integration/smart-compaction.test.js +230 -0
- package/dist/__tests__/integration/summarization.test.js +308 -0
- package/dist/__tests__/integration/tokenLimitEnforcement.test.js +134 -0
- package/dist/__tests__/integration/tool-profiles-integration.test.js +150 -0
- package/dist/__tests__/integration/watcher-migration-validation.test.js +544 -0
- package/dist/__tests__/security/input-validation.test.js +115 -0
- package/dist/__tests__/utils/agents.test.js +473 -0
- package/dist/__tests__/utils/database.test.js +177 -0
- package/dist/__tests__/utils/git.test.js +122 -0
- package/dist/__tests__/utils/knowledge-graph.test.js +297 -0
- package/dist/__tests__/utils/migrationHealthCheck.test.js +302 -0
- package/dist/__tests__/utils/project-directory-messages.test.js +192 -0
- package/dist/__tests__/utils/timezone-safe-dates.js +119 -0
- package/dist/__tests__/utils/token-limits.test.js +225 -0
- package/dist/__tests__/utils/tool-profiles.test.js +374 -0
- package/dist/__tests__/utils/validation.test.js +200 -0
- package/dist/__tests__/utils/vector-store.test.js +231 -0
- package/dist/handlers/contextWatchHandlers.js +206 -0
- package/dist/index.js +4425 -0
- package/dist/migrations/003_add_channels.js +174 -0
- package/dist/migrations/004_add_context_watch.js +151 -0
- package/dist/migrations/005_add_context_watch.js +98 -0
- package/dist/migrations/simplify-sharing.js +117 -0
- package/dist/repositories/BaseRepository.js +30 -0
- package/dist/repositories/CheckpointRepository.js +140 -0
- package/dist/repositories/ContextRepository.js +2017 -0
- package/dist/repositories/FileRepository.js +104 -0
- package/dist/repositories/RepositoryManager.js +62 -0
- package/dist/repositories/SessionRepository.js +66 -0
- package/dist/repositories/WatcherRepository.js +252 -0
- package/dist/repositories/index.js +15 -0
- package/dist/test-helpers/database-helper.js +128 -0
- package/dist/types/entities.js +3 -0
- package/dist/utils/agents.js +791 -0
- package/dist/utils/channels.js +150 -0
- package/dist/utils/database.js +780 -0
- package/dist/utils/feature-flags.js +476 -0
- package/dist/utils/git.js +145 -0
- package/dist/utils/knowledge-graph.js +264 -0
- package/dist/utils/migrationHealthCheck.js +373 -0
- package/dist/utils/migrations.js +452 -0
- package/dist/utils/retention.js +460 -0
- package/dist/utils/timestamps.js +112 -0
- package/dist/utils/token-limits.js +350 -0
- package/dist/utils/tool-profiles.js +242 -0
- package/dist/utils/validation.js +296 -0
- package/dist/utils/vector-store.js +247 -0
- package/examples/config.json +31 -0
- package/examples/project-directory-setup.md +114 -0
- package/package.json +85 -0
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Test to validate the fix for issue #24
|
|
3
|
+
// Tests the actual flow through index.ts handler
|
|
4
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
5
|
+
const globals_1 = require("@jest/globals");
|
|
6
|
+
// Mock the functions used in index.ts for context_get
|
|
7
|
+
function estimateTokens(text) {
|
|
8
|
+
return Math.ceil(text.length / 4);
|
|
9
|
+
}
|
|
10
|
+
function calculateSize(value) {
|
|
11
|
+
return Buffer.byteLength(value, 'utf8');
|
|
12
|
+
}
|
|
13
|
+
function calculateResponseMetrics(items) {
|
|
14
|
+
let totalSize = 0;
|
|
15
|
+
for (const item of items) {
|
|
16
|
+
const itemSize = item.size || calculateSize(item.value);
|
|
17
|
+
totalSize += itemSize;
|
|
18
|
+
}
|
|
19
|
+
// Convert to JSON string to get actual response size
|
|
20
|
+
const jsonString = JSON.stringify(items);
|
|
21
|
+
const estimatedTokens = estimateTokens(jsonString);
|
|
22
|
+
const averageSize = items.length > 0 ? Math.round(totalSize / items.length) : 0;
|
|
23
|
+
return { totalSize, estimatedTokens, averageSize };
|
|
24
|
+
}
|
|
25
|
+
(0, globals_1.describe)('Issue #24 Fix Validation', () => {
|
|
26
|
+
(0, globals_1.it)('should correctly calculate tokens for response with metadata', () => {
|
|
27
|
+
// Create sample items as they would come from the database
|
|
28
|
+
const dbItems = [];
|
|
29
|
+
for (let i = 0; i < 100; i++) {
|
|
30
|
+
dbItems.push({
|
|
31
|
+
id: `id-${i}`,
|
|
32
|
+
session_id: 'test-session',
|
|
33
|
+
key: `test_item_${i}`,
|
|
34
|
+
value: `This is test content that is moderately long to simulate real data. `.repeat(3),
|
|
35
|
+
category: 'task',
|
|
36
|
+
priority: 'high',
|
|
37
|
+
channel: 'test',
|
|
38
|
+
metadata: JSON.stringify({ index: i }),
|
|
39
|
+
size: 200,
|
|
40
|
+
created_at: '2025-01-20T10:00:00Z',
|
|
41
|
+
updated_at: '2025-01-20T10:00:00Z',
|
|
42
|
+
});
|
|
43
|
+
}
|
|
44
|
+
// Test without metadata (original calculation)
|
|
45
|
+
const metricsWithoutMetadata = calculateResponseMetrics(dbItems);
|
|
46
|
+
console.log('Without metadata - Items:', dbItems.length, 'Tokens:', metricsWithoutMetadata.estimatedTokens);
|
|
47
|
+
// Test with metadata (as per the fix)
|
|
48
|
+
const itemsWithMetadata = dbItems.map(item => ({
|
|
49
|
+
key: item.key,
|
|
50
|
+
value: item.value,
|
|
51
|
+
category: item.category,
|
|
52
|
+
priority: item.priority,
|
|
53
|
+
channel: item.channel,
|
|
54
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
55
|
+
size: item.size || calculateSize(item.value),
|
|
56
|
+
created_at: item.created_at,
|
|
57
|
+
updated_at: item.updated_at,
|
|
58
|
+
}));
|
|
59
|
+
const metricsWithMetadata = calculateResponseMetrics(itemsWithMetadata);
|
|
60
|
+
console.log('With metadata - Items:', itemsWithMetadata.length, 'Tokens:', metricsWithMetadata.estimatedTokens);
|
|
61
|
+
// The metadata version might have fewer tokens due to JSON parsing
|
|
62
|
+
// but the overall response structure adds overhead
|
|
63
|
+
// Build full response structure as the handler does
|
|
64
|
+
const fullResponse = {
|
|
65
|
+
items: itemsWithMetadata,
|
|
66
|
+
pagination: {
|
|
67
|
+
total: 100,
|
|
68
|
+
returned: 100,
|
|
69
|
+
offset: 0,
|
|
70
|
+
hasMore: false,
|
|
71
|
+
nextOffset: null,
|
|
72
|
+
totalCount: 100,
|
|
73
|
+
page: 1,
|
|
74
|
+
pageSize: 100,
|
|
75
|
+
totalPages: 1,
|
|
76
|
+
hasNextPage: false,
|
|
77
|
+
hasPreviousPage: false,
|
|
78
|
+
previousOffset: null,
|
|
79
|
+
totalSize: metricsWithMetadata.totalSize,
|
|
80
|
+
averageSize: metricsWithMetadata.averageSize,
|
|
81
|
+
defaultsApplied: { limit: true, sort: true },
|
|
82
|
+
truncated: false,
|
|
83
|
+
truncatedCount: 0,
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
const finalResponseJson = JSON.stringify(fullResponse, null, 2);
|
|
87
|
+
const finalTokens = estimateTokens(finalResponseJson);
|
|
88
|
+
console.log('Final response - Size:', finalResponseJson.length, 'Tokens:', finalTokens);
|
|
89
|
+
// This demonstrates the issue: the final response can be much larger
|
|
90
|
+
// than what calculateResponseMetrics estimates
|
|
91
|
+
console.log('Token difference:', finalTokens - metricsWithMetadata.estimatedTokens);
|
|
92
|
+
// With 100 items and metadata, we should be approaching or exceeding limits
|
|
93
|
+
if (finalTokens > 18000) {
|
|
94
|
+
console.log('WARNING: Response exceeds safe token limit!');
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
(0, globals_1.it)('should demonstrate that 50 items with metadata stays under limit', () => {
|
|
98
|
+
// Create sample items
|
|
99
|
+
const dbItems = [];
|
|
100
|
+
for (let i = 0; i < 50; i++) {
|
|
101
|
+
dbItems.push({
|
|
102
|
+
id: `id-${i}`,
|
|
103
|
+
session_id: 'test-session',
|
|
104
|
+
key: `test_item_${i}`,
|
|
105
|
+
value: `This is test content that is moderately long to simulate real data. `.repeat(3),
|
|
106
|
+
category: 'task',
|
|
107
|
+
priority: 'high',
|
|
108
|
+
channel: 'test',
|
|
109
|
+
metadata: JSON.stringify({ index: i }),
|
|
110
|
+
size: 200,
|
|
111
|
+
created_at: '2025-01-20T10:00:00Z',
|
|
112
|
+
updated_at: '2025-01-20T10:00:00Z',
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Transform with metadata
|
|
116
|
+
const itemsWithMetadata = dbItems.map(item => ({
|
|
117
|
+
key: item.key,
|
|
118
|
+
value: item.value,
|
|
119
|
+
category: item.category,
|
|
120
|
+
priority: item.priority,
|
|
121
|
+
channel: item.channel,
|
|
122
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
123
|
+
size: item.size || calculateSize(item.value),
|
|
124
|
+
created_at: item.created_at,
|
|
125
|
+
updated_at: item.updated_at,
|
|
126
|
+
}));
|
|
127
|
+
const metrics = calculateResponseMetrics(itemsWithMetadata);
|
|
128
|
+
// Build full response
|
|
129
|
+
const fullResponse = {
|
|
130
|
+
items: itemsWithMetadata,
|
|
131
|
+
pagination: {
|
|
132
|
+
total: 50,
|
|
133
|
+
returned: 50,
|
|
134
|
+
offset: 0,
|
|
135
|
+
hasMore: false,
|
|
136
|
+
nextOffset: null,
|
|
137
|
+
totalCount: 50,
|
|
138
|
+
page: 1,
|
|
139
|
+
pageSize: 50,
|
|
140
|
+
totalPages: 1,
|
|
141
|
+
hasNextPage: false,
|
|
142
|
+
hasPreviousPage: false,
|
|
143
|
+
previousOffset: null,
|
|
144
|
+
totalSize: metrics.totalSize,
|
|
145
|
+
averageSize: metrics.averageSize,
|
|
146
|
+
defaultsApplied: { limit: true, sort: true },
|
|
147
|
+
truncated: false,
|
|
148
|
+
truncatedCount: 0,
|
|
149
|
+
},
|
|
150
|
+
};
|
|
151
|
+
const finalResponseJson = JSON.stringify(fullResponse, null, 2);
|
|
152
|
+
const finalTokens = estimateTokens(finalResponseJson);
|
|
153
|
+
console.log('50 items with metadata - Tokens:', finalTokens);
|
|
154
|
+
// 50 items should be safe
|
|
155
|
+
(0, globals_1.expect)(finalTokens).toBeLessThan(18000);
|
|
156
|
+
(0, globals_1.expect)(finalTokens).toBeLessThan(25000); // Well under MCP limit
|
|
157
|
+
});
|
|
158
|
+
});
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
// Attempt to reproduce the exact issue from #24
|
|
3
|
+
// User reported: "Error: MCP tool "context_get" response (26866 tokens) exceeds maximum allowed tokens (25000)"
|
|
4
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
5
|
+
if (k2 === undefined) k2 = k;
|
|
6
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
7
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
8
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
9
|
+
}
|
|
10
|
+
Object.defineProperty(o, k2, desc);
|
|
11
|
+
}) : (function(o, m, k, k2) {
|
|
12
|
+
if (k2 === undefined) k2 = k;
|
|
13
|
+
o[k2] = m[k];
|
|
14
|
+
}));
|
|
15
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
16
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
17
|
+
}) : function(o, v) {
|
|
18
|
+
o["default"] = v;
|
|
19
|
+
});
|
|
20
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
21
|
+
var ownKeys = function(o) {
|
|
22
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
23
|
+
var ar = [];
|
|
24
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
25
|
+
return ar;
|
|
26
|
+
};
|
|
27
|
+
return ownKeys(o);
|
|
28
|
+
};
|
|
29
|
+
return function (mod) {
|
|
30
|
+
if (mod && mod.__esModule) return mod;
|
|
31
|
+
var result = {};
|
|
32
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
33
|
+
__setModuleDefault(result, mod);
|
|
34
|
+
return result;
|
|
35
|
+
};
|
|
36
|
+
})();
|
|
37
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
38
|
+
const database_1 = require("../../utils/database");
|
|
39
|
+
const RepositoryManager_1 = require("../../repositories/RepositoryManager");
|
|
40
|
+
const os = __importStar(require("os"));
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const fs = __importStar(require("fs"));
|
|
43
|
+
function estimateTokens(text) {
|
|
44
|
+
return Math.ceil(text.length / 4);
|
|
45
|
+
}
|
|
46
|
+
function calculateSize(value) {
|
|
47
|
+
return Buffer.byteLength(value, 'utf8');
|
|
48
|
+
}
|
|
49
|
+
describe('Issue #24 - Reproduce exact error', () => {
|
|
50
|
+
let dbManager;
|
|
51
|
+
let repositories;
|
|
52
|
+
let tempDbPath;
|
|
53
|
+
let testSessionId;
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
tempDbPath = path.join(os.tmpdir(), `test-issue24-reproduce-${Date.now()}.db`);
|
|
56
|
+
dbManager = new database_1.DatabaseManager({
|
|
57
|
+
filename: tempDbPath,
|
|
58
|
+
maxSize: 10 * 1024 * 1024,
|
|
59
|
+
walMode: true,
|
|
60
|
+
});
|
|
61
|
+
repositories = new RepositoryManager_1.RepositoryManager(dbManager);
|
|
62
|
+
// Create test session
|
|
63
|
+
const session = repositories.sessions.create({
|
|
64
|
+
name: 'Issue #24 Reproduce',
|
|
65
|
+
description: 'Attempting to reproduce exact token overflow',
|
|
66
|
+
});
|
|
67
|
+
testSessionId = session.id;
|
|
68
|
+
});
|
|
69
|
+
afterEach(() => {
|
|
70
|
+
dbManager.close();
|
|
71
|
+
try {
|
|
72
|
+
fs.unlinkSync(tempDbPath);
|
|
73
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
74
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
75
|
+
}
|
|
76
|
+
catch (_e) {
|
|
77
|
+
// Ignore
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
it('should reproduce token overflow with realistic data', () => {
|
|
81
|
+
// To get 26866 tokens, we need roughly 107,464 characters
|
|
82
|
+
// Let's create items with realistic content that would cause this
|
|
83
|
+
const itemCount = 150; // More items than default limit
|
|
84
|
+
const largeContent = `
|
|
85
|
+
This is a realistic context item that might be saved during a coding session.
|
|
86
|
+
It contains multiple lines of information, including:
|
|
87
|
+
- Task descriptions and implementation details
|
|
88
|
+
- Code snippets and examples
|
|
89
|
+
- Decision rationale and architectural notes
|
|
90
|
+
- Progress updates and status information
|
|
91
|
+
- Error messages and debugging information
|
|
92
|
+
- References to files and functions
|
|
93
|
+
- Links and external resources
|
|
94
|
+
|
|
95
|
+
The content is substantial enough to represent real-world usage where developers
|
|
96
|
+
save important context about their work. This might include detailed explanations
|
|
97
|
+
of complex logic, API documentation, configuration settings, or troubleshooting notes.
|
|
98
|
+
|
|
99
|
+
Additional metadata might include timestamps, categories, priorities, and custom
|
|
100
|
+
tags that help organize and retrieve the information later. All of this contributes
|
|
101
|
+
to the overall size of the response when multiple items are returned together.
|
|
102
|
+
`.trim();
|
|
103
|
+
// Create many items with substantial content
|
|
104
|
+
for (let i = 0; i < itemCount; i++) {
|
|
105
|
+
repositories.contexts.save(testSessionId, {
|
|
106
|
+
key: `context_item_${String(i).padStart(3, '0')}`,
|
|
107
|
+
value: `${largeContent}\n\nItem ${i} specific notes: ${String(i).repeat(20)}`,
|
|
108
|
+
category: ['task', 'decision', 'progress', 'note'][i % 4],
|
|
109
|
+
priority: ['high', 'normal', 'low'][i % 3],
|
|
110
|
+
channel: `channel-${i % 5}`,
|
|
111
|
+
metadata: JSON.stringify({
|
|
112
|
+
index: i,
|
|
113
|
+
timestamp: new Date().toISOString(),
|
|
114
|
+
tags: ['important', 'review', 'todo', 'done'][i % 4],
|
|
115
|
+
additionalData: {
|
|
116
|
+
relatedFiles: [`file${i}.ts`, `test${i}.spec.ts`],
|
|
117
|
+
relatedIssues: [`#${i * 10}`, `#${i * 10 + 1}`],
|
|
118
|
+
},
|
|
119
|
+
}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
// Query without specifying limit (should use default)
|
|
123
|
+
const result = repositories.contexts.queryEnhanced({
|
|
124
|
+
sessionId: testSessionId,
|
|
125
|
+
includeMetadata: true,
|
|
126
|
+
// No limit specified - will use repository default of 100
|
|
127
|
+
});
|
|
128
|
+
console.log(`Created ${itemCount} items, retrieved ${result.items.length} items`);
|
|
129
|
+
// Transform items with metadata as the handler does
|
|
130
|
+
const itemsWithMetadata = result.items.map(item => ({
|
|
131
|
+
key: item.key,
|
|
132
|
+
value: item.value,
|
|
133
|
+
category: item.category,
|
|
134
|
+
priority: item.priority,
|
|
135
|
+
channel: item.channel,
|
|
136
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
137
|
+
size: item.size || calculateSize(item.value),
|
|
138
|
+
created_at: item.created_at,
|
|
139
|
+
updated_at: item.updated_at,
|
|
140
|
+
}));
|
|
141
|
+
// Create the full response structure
|
|
142
|
+
const fullResponse = {
|
|
143
|
+
items: itemsWithMetadata,
|
|
144
|
+
pagination: {
|
|
145
|
+
total: result.totalCount,
|
|
146
|
+
returned: result.items.length,
|
|
147
|
+
offset: 0,
|
|
148
|
+
hasMore: result.totalCount > result.items.length,
|
|
149
|
+
nextOffset: result.items.length < result.totalCount ? result.items.length : null,
|
|
150
|
+
totalCount: result.totalCount,
|
|
151
|
+
page: 1,
|
|
152
|
+
pageSize: result.items.length,
|
|
153
|
+
totalPages: Math.ceil(result.totalCount / result.items.length),
|
|
154
|
+
hasNextPage: result.totalCount > result.items.length,
|
|
155
|
+
hasPreviousPage: false,
|
|
156
|
+
previousOffset: null,
|
|
157
|
+
totalSize: itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0),
|
|
158
|
+
averageSize: Math.round(itemsWithMetadata.reduce((sum, item) => sum + (item.size || 0), 0) / result.items.length),
|
|
159
|
+
defaultsApplied: { limit: true, sort: true },
|
|
160
|
+
truncated: false,
|
|
161
|
+
truncatedCount: 0,
|
|
162
|
+
},
|
|
163
|
+
};
|
|
164
|
+
const responseJson = JSON.stringify(fullResponse, null, 2);
|
|
165
|
+
const tokens = estimateTokens(responseJson);
|
|
166
|
+
console.log(`Response size: ${responseJson.length} bytes`);
|
|
167
|
+
console.log(`Estimated tokens: ${tokens}`);
|
|
168
|
+
console.log(`Average item size: ${Math.round(responseJson.length / result.items.length)} bytes`);
|
|
169
|
+
// Check if we're reproducing the issue
|
|
170
|
+
if (tokens > 25000) {
|
|
171
|
+
console.log('✅ Successfully reproduced token overflow!');
|
|
172
|
+
console.log(`Tokens: ${tokens} > 25000 limit`);
|
|
173
|
+
}
|
|
174
|
+
else if (tokens > 20000) {
|
|
175
|
+
console.log('⚠️ Approaching token limit but not exceeding');
|
|
176
|
+
}
|
|
177
|
+
else {
|
|
178
|
+
console.log('❌ Did not reproduce token overflow');
|
|
179
|
+
console.log('Need larger items or more items to reproduce');
|
|
180
|
+
}
|
|
181
|
+
// The fix should prevent this from happening
|
|
182
|
+
// With our changes:
|
|
183
|
+
// 1. Default limit is 50 when includeMetadata is true (at handler level)
|
|
184
|
+
// 2. Token limit is 18000 (more conservative)
|
|
185
|
+
// 3. Response is truncated if it exceeds limits
|
|
186
|
+
});
|
|
187
|
+
it('should show how the fix prevents overflow', () => {
|
|
188
|
+
// Same setup but showing how limit of 50 prevents the issue
|
|
189
|
+
const itemCount = 150;
|
|
190
|
+
const largeContent = `Large content...`.repeat(50); // Smaller for this test
|
|
191
|
+
for (let i = 0; i < itemCount; i++) {
|
|
192
|
+
repositories.contexts.save(testSessionId, {
|
|
193
|
+
key: `item_${i}`,
|
|
194
|
+
value: largeContent,
|
|
195
|
+
category: 'task',
|
|
196
|
+
priority: 'high',
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
// With the fix, when includeMetadata is true, default limit should be 50
|
|
200
|
+
// But this is handled at the handler level, not repository level
|
|
201
|
+
// Repository still defaults to 100
|
|
202
|
+
const resultWith50 = repositories.contexts.queryEnhanced({
|
|
203
|
+
sessionId: testSessionId,
|
|
204
|
+
includeMetadata: true,
|
|
205
|
+
limit: 50, // Explicitly set to what our fix would use
|
|
206
|
+
});
|
|
207
|
+
const itemsWithMetadata = resultWith50.items.map(item => ({
|
|
208
|
+
key: item.key,
|
|
209
|
+
value: item.value,
|
|
210
|
+
category: item.category,
|
|
211
|
+
priority: item.priority,
|
|
212
|
+
channel: item.channel,
|
|
213
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
214
|
+
size: item.size || calculateSize(item.value),
|
|
215
|
+
created_at: item.created_at,
|
|
216
|
+
updated_at: item.updated_at,
|
|
217
|
+
}));
|
|
218
|
+
const responseJson = JSON.stringify({ items: itemsWithMetadata }, null, 2);
|
|
219
|
+
const tokens = estimateTokens(responseJson);
|
|
220
|
+
console.log(`With limit=50: ${resultWith50.items.length} items, ${tokens} tokens`);
|
|
221
|
+
// Should be well under limit
|
|
222
|
+
expect(tokens).toBeLessThan(18000);
|
|
223
|
+
expect(tokens).toBeLessThan(25000);
|
|
224
|
+
});
|
|
225
|
+
});
|
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
const database_1 = require("../../utils/database");
|
|
37
|
+
const RepositoryManager_1 = require("../../repositories/RepositoryManager");
|
|
38
|
+
const os = __importStar(require("os"));
|
|
39
|
+
const path = __importStar(require("path"));
|
|
40
|
+
const fs = __importStar(require("fs"));
|
|
41
|
+
describe('Issue #24 - Token Limit with includeMetadata', () => {
|
|
42
|
+
let dbManager;
|
|
43
|
+
let repositories;
|
|
44
|
+
let tempDbPath;
|
|
45
|
+
let testSessionId;
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
tempDbPath = path.join(os.tmpdir(), `test-issue24-${Date.now()}.db`);
|
|
48
|
+
dbManager = new database_1.DatabaseManager({
|
|
49
|
+
filename: tempDbPath,
|
|
50
|
+
maxSize: 10 * 1024 * 1024,
|
|
51
|
+
walMode: true,
|
|
52
|
+
});
|
|
53
|
+
repositories = new RepositoryManager_1.RepositoryManager(dbManager);
|
|
54
|
+
// Create test session
|
|
55
|
+
const session = repositories.sessions.create({
|
|
56
|
+
name: 'Issue #24 Test Session',
|
|
57
|
+
description: 'Testing token limit with metadata',
|
|
58
|
+
});
|
|
59
|
+
testSessionId = session.id;
|
|
60
|
+
});
|
|
61
|
+
afterEach(() => {
|
|
62
|
+
dbManager.close();
|
|
63
|
+
try {
|
|
64
|
+
fs.unlinkSync(tempDbPath);
|
|
65
|
+
fs.unlinkSync(`${tempDbPath}-wal`);
|
|
66
|
+
fs.unlinkSync(`${tempDbPath}-shm`);
|
|
67
|
+
}
|
|
68
|
+
catch (_e) {
|
|
69
|
+
// Ignore
|
|
70
|
+
}
|
|
71
|
+
});
|
|
72
|
+
it('should not exceed token limit when includeMetadata is true with many items', () => {
|
|
73
|
+
// Create many items with substantial content to simulate real-world scenario
|
|
74
|
+
const itemCount = 200; // Create enough items to potentially exceed token limits
|
|
75
|
+
for (let i = 0; i < itemCount; i++) {
|
|
76
|
+
repositories.contexts.save(testSessionId, {
|
|
77
|
+
key: `test_item_${i}`,
|
|
78
|
+
value: `This is a test item with a moderately long value that simulates real-world context data.
|
|
79
|
+
It includes multiple lines and enough content to make the response substantial when
|
|
80
|
+
many items are returned together. Each item contributes to the total token count.
|
|
81
|
+
Item number: ${i}. Additional metadata and timestamps will be included when
|
|
82
|
+
includeMetadata is set to true, further increasing the response size.`.repeat(2),
|
|
83
|
+
category: i % 2 === 0 ? 'task' : 'decision',
|
|
84
|
+
priority: i % 3 === 0 ? 'high' : 'normal',
|
|
85
|
+
channel: 'test-channel',
|
|
86
|
+
metadata: JSON.stringify({
|
|
87
|
+
index: i,
|
|
88
|
+
timestamp: new Date().toISOString(),
|
|
89
|
+
tags: ['test', 'issue24', 'metadata'],
|
|
90
|
+
}),
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
// Query with includeMetadata: true (this was causing the issue)
|
|
94
|
+
const result = repositories.contexts.queryEnhanced({
|
|
95
|
+
sessionId: testSessionId,
|
|
96
|
+
includeMetadata: true,
|
|
97
|
+
// No limit specified - repository uses default of 100
|
|
98
|
+
// The handler level would apply dynamic limits, but repository doesn't know about includeMetadata
|
|
99
|
+
});
|
|
100
|
+
// Repository default is 100 items (it doesn't know about metadata)
|
|
101
|
+
// The dynamic limiting happens at the handler level in index.ts
|
|
102
|
+
expect(result.items.length).toBeLessThanOrEqual(100);
|
|
103
|
+
expect(result.totalCount).toBe(itemCount);
|
|
104
|
+
// Simulate the response construction with metadata
|
|
105
|
+
const itemsWithMetadata = result.items.map(item => ({
|
|
106
|
+
key: item.key,
|
|
107
|
+
value: item.value,
|
|
108
|
+
category: item.category,
|
|
109
|
+
priority: item.priority,
|
|
110
|
+
channel: item.channel,
|
|
111
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
112
|
+
size: item.size || Buffer.byteLength(item.value, 'utf8'),
|
|
113
|
+
created_at: item.created_at,
|
|
114
|
+
updated_at: item.updated_at,
|
|
115
|
+
}));
|
|
116
|
+
// Create the full response structure
|
|
117
|
+
const response = {
|
|
118
|
+
items: itemsWithMetadata,
|
|
119
|
+
pagination: {
|
|
120
|
+
total: result.totalCount,
|
|
121
|
+
returned: result.items.length,
|
|
122
|
+
offset: 0,
|
|
123
|
+
hasMore: result.totalCount > result.items.length,
|
|
124
|
+
nextOffset: result.items.length < result.totalCount ? result.items.length : null,
|
|
125
|
+
totalCount: result.totalCount,
|
|
126
|
+
page: 1,
|
|
127
|
+
pageSize: 50,
|
|
128
|
+
totalPages: Math.ceil(result.totalCount / 50),
|
|
129
|
+
hasNextPage: result.totalCount > result.items.length,
|
|
130
|
+
hasPreviousPage: false,
|
|
131
|
+
previousOffset: null,
|
|
132
|
+
totalSize: 0,
|
|
133
|
+
averageSize: 0,
|
|
134
|
+
defaultsApplied: { limit: true, sort: true },
|
|
135
|
+
truncated: false,
|
|
136
|
+
truncatedCount: 0,
|
|
137
|
+
},
|
|
138
|
+
};
|
|
139
|
+
// Calculate token estimate for the response
|
|
140
|
+
const responseJson = JSON.stringify(response, null, 2);
|
|
141
|
+
const estimatedTokens = Math.ceil(responseJson.length / 4);
|
|
142
|
+
console.log(`Response size: ${responseJson.length} bytes`);
|
|
143
|
+
console.log(`Estimated tokens: ${estimatedTokens}`);
|
|
144
|
+
console.log(`Items returned: ${result.items.length} of ${result.totalCount}`);
|
|
145
|
+
// Note: Repository returns 100 items by default, which may exceed token limits
|
|
146
|
+
// This demonstrates why the handler level needs to apply dynamic limiting
|
|
147
|
+
// The handler would truncate this response to prevent token overflow
|
|
148
|
+
console.log(`Repository returned ${result.items.length} items with ${estimatedTokens} tokens`);
|
|
149
|
+
// This shows the problem: repository default of 100 can exceed limits
|
|
150
|
+
if (estimatedTokens > 25000) {
|
|
151
|
+
console.log('This demonstrates why dynamic limiting at handler level is needed!');
|
|
152
|
+
}
|
|
153
|
+
});
|
|
154
|
+
it('should handle explicit high limit with metadata by truncating', () => {
|
|
155
|
+
// Create many items
|
|
156
|
+
const itemCount = 500;
|
|
157
|
+
for (let i = 0; i < itemCount; i++) {
|
|
158
|
+
repositories.contexts.save(testSessionId, {
|
|
159
|
+
key: `large_item_${i}`,
|
|
160
|
+
value: `Large content ${i}`.repeat(50), // Make items larger
|
|
161
|
+
category: 'task',
|
|
162
|
+
priority: 'high',
|
|
163
|
+
channel: 'large-channel',
|
|
164
|
+
});
|
|
165
|
+
}
|
|
166
|
+
// Query with a high explicit limit and metadata
|
|
167
|
+
const result = repositories.contexts.queryEnhanced({
|
|
168
|
+
sessionId: testSessionId,
|
|
169
|
+
includeMetadata: true,
|
|
170
|
+
limit: 1000, // Explicitly request many items
|
|
171
|
+
});
|
|
172
|
+
// Repository will cap at the actual number of items available
|
|
173
|
+
// Since we created 500 items, we should get all 500
|
|
174
|
+
// The token limiting would happen at the handler level, not repository level
|
|
175
|
+
expect(result.items.length).toBe(500); // Gets all items since limit > itemCount
|
|
176
|
+
// Calculate response size
|
|
177
|
+
const itemsWithMetadata = result.items.map(item => ({
|
|
178
|
+
key: item.key,
|
|
179
|
+
value: item.value,
|
|
180
|
+
category: item.category,
|
|
181
|
+
priority: item.priority,
|
|
182
|
+
channel: item.channel,
|
|
183
|
+
metadata: item.metadata ? JSON.parse(item.metadata) : null,
|
|
184
|
+
size: item.size || Buffer.byteLength(item.value, 'utf8'),
|
|
185
|
+
created_at: item.created_at,
|
|
186
|
+
updated_at: item.updated_at,
|
|
187
|
+
}));
|
|
188
|
+
const responseJson = JSON.stringify({ items: itemsWithMetadata }, null, 2);
|
|
189
|
+
const estimatedTokens = Math.ceil(responseJson.length / 4);
|
|
190
|
+
console.log(`Large query - Items: ${result.items.length}, Tokens: ${estimatedTokens}`);
|
|
191
|
+
// This test shows that without handler-level limiting, large queries would overflow
|
|
192
|
+
// The handler's dynamic limiting would truncate this to safe levels
|
|
193
|
+
if (estimatedTokens > 25000) {
|
|
194
|
+
console.log(`Would need truncation: ${estimatedTokens} tokens exceeds 25000 limit`);
|
|
195
|
+
}
|
|
196
|
+
// The test demonstrates the need for handler-level dynamic limiting
|
|
197
|
+
expect(result.items.length).toBe(500); // Repository returns what was requested
|
|
198
|
+
});
|
|
199
|
+
});
|