mcp-rubber-duck 1.1.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/.dockerignore +19 -0
- package/.env.desktop.example +145 -0
- package/.env.example +45 -0
- package/.env.pi.example +106 -0
- package/.env.template +165 -0
- package/.eslintrc.json +40 -0
- package/.github/ISSUE_TEMPLATE/bug_report.md +65 -0
- package/.github/ISSUE_TEMPLATE/feature_request.md +58 -0
- package/.github/ISSUE_TEMPLATE/question.md +67 -0
- package/.github/pull_request_template.md +111 -0
- package/.github/workflows/docker-build.yml +138 -0
- package/.github/workflows/release.yml +182 -0
- package/.github/workflows/security.yml +141 -0
- package/.github/workflows/semantic-release.yml +89 -0
- package/.prettierrc +10 -0
- package/.releaserc.json +66 -0
- package/CHANGELOG.md +95 -0
- package/CONTRIBUTING.md +242 -0
- package/Dockerfile +62 -0
- package/LICENSE +21 -0
- package/README.md +803 -0
- package/audit-ci.json +8 -0
- package/config/claude_desktop.json +14 -0
- package/config/config.example.json +91 -0
- package/dist/config/config.d.ts +51 -0
- package/dist/config/config.d.ts.map +1 -0
- package/dist/config/config.js +301 -0
- package/dist/config/config.js.map +1 -0
- package/dist/config/types.d.ts +356 -0
- package/dist/config/types.d.ts.map +1 -0
- package/dist/config/types.js +41 -0
- package/dist/config/types.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +109 -0
- package/dist/index.js.map +1 -0
- package/dist/providers/duck-provider-enhanced.d.ts +29 -0
- package/dist/providers/duck-provider-enhanced.d.ts.map +1 -0
- package/dist/providers/duck-provider-enhanced.js +230 -0
- package/dist/providers/duck-provider-enhanced.js.map +1 -0
- package/dist/providers/enhanced-manager.d.ts +54 -0
- package/dist/providers/enhanced-manager.d.ts.map +1 -0
- package/dist/providers/enhanced-manager.js +217 -0
- package/dist/providers/enhanced-manager.js.map +1 -0
- package/dist/providers/manager.d.ts +28 -0
- package/dist/providers/manager.d.ts.map +1 -0
- package/dist/providers/manager.js +204 -0
- package/dist/providers/manager.js.map +1 -0
- package/dist/providers/provider.d.ts +29 -0
- package/dist/providers/provider.d.ts.map +1 -0
- package/dist/providers/provider.js +179 -0
- package/dist/providers/provider.js.map +1 -0
- package/dist/providers/types.d.ts +69 -0
- package/dist/providers/types.d.ts.map +1 -0
- package/dist/providers/types.js +2 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/server.d.ts +24 -0
- package/dist/server.d.ts.map +1 -0
- package/dist/server.js +501 -0
- package/dist/server.js.map +1 -0
- package/dist/services/approval.d.ts +44 -0
- package/dist/services/approval.d.ts.map +1 -0
- package/dist/services/approval.js +159 -0
- package/dist/services/approval.js.map +1 -0
- package/dist/services/cache.d.ts +21 -0
- package/dist/services/cache.d.ts.map +1 -0
- package/dist/services/cache.js +63 -0
- package/dist/services/cache.js.map +1 -0
- package/dist/services/conversation.d.ts +24 -0
- package/dist/services/conversation.d.ts.map +1 -0
- package/dist/services/conversation.js +108 -0
- package/dist/services/conversation.js.map +1 -0
- package/dist/services/function-bridge.d.ts +41 -0
- package/dist/services/function-bridge.d.ts.map +1 -0
- package/dist/services/function-bridge.js +259 -0
- package/dist/services/function-bridge.js.map +1 -0
- package/dist/services/health.d.ts +17 -0
- package/dist/services/health.d.ts.map +1 -0
- package/dist/services/health.js +77 -0
- package/dist/services/health.js.map +1 -0
- package/dist/services/mcp-client-manager.d.ts +49 -0
- package/dist/services/mcp-client-manager.d.ts.map +1 -0
- package/dist/services/mcp-client-manager.js +279 -0
- package/dist/services/mcp-client-manager.js.map +1 -0
- package/dist/tools/approve-mcp-request.d.ts +9 -0
- package/dist/tools/approve-mcp-request.d.ts.map +1 -0
- package/dist/tools/approve-mcp-request.js +111 -0
- package/dist/tools/approve-mcp-request.js.map +1 -0
- package/dist/tools/ask-duck.d.ts +9 -0
- package/dist/tools/ask-duck.d.ts.map +1 -0
- package/dist/tools/ask-duck.js +43 -0
- package/dist/tools/ask-duck.js.map +1 -0
- package/dist/tools/chat-duck.d.ts +9 -0
- package/dist/tools/chat-duck.d.ts.map +1 -0
- package/dist/tools/chat-duck.js +57 -0
- package/dist/tools/chat-duck.js.map +1 -0
- package/dist/tools/clear-conversations.d.ts +8 -0
- package/dist/tools/clear-conversations.d.ts.map +1 -0
- package/dist/tools/clear-conversations.js +17 -0
- package/dist/tools/clear-conversations.js.map +1 -0
- package/dist/tools/compare-ducks.d.ts +8 -0
- package/dist/tools/compare-ducks.d.ts.map +1 -0
- package/dist/tools/compare-ducks.js +49 -0
- package/dist/tools/compare-ducks.js.map +1 -0
- package/dist/tools/duck-council.d.ts +8 -0
- package/dist/tools/duck-council.d.ts.map +1 -0
- package/dist/tools/duck-council.js +69 -0
- package/dist/tools/duck-council.js.map +1 -0
- package/dist/tools/get-pending-approvals.d.ts +15 -0
- package/dist/tools/get-pending-approvals.d.ts.map +1 -0
- package/dist/tools/get-pending-approvals.js +74 -0
- package/dist/tools/get-pending-approvals.js.map +1 -0
- package/dist/tools/list-ducks.d.ts +9 -0
- package/dist/tools/list-ducks.d.ts.map +1 -0
- package/dist/tools/list-ducks.js +47 -0
- package/dist/tools/list-ducks.js.map +1 -0
- package/dist/tools/list-models.d.ts +8 -0
- package/dist/tools/list-models.d.ts.map +1 -0
- package/dist/tools/list-models.js +72 -0
- package/dist/tools/list-models.js.map +1 -0
- package/dist/tools/mcp-status.d.ts +17 -0
- package/dist/tools/mcp-status.d.ts.map +1 -0
- package/dist/tools/mcp-status.js +100 -0
- package/dist/tools/mcp-status.js.map +1 -0
- package/dist/utils/ascii-art.d.ts +19 -0
- package/dist/utils/ascii-art.d.ts.map +1 -0
- package/dist/utils/ascii-art.js +73 -0
- package/dist/utils/ascii-art.js.map +1 -0
- package/dist/utils/logger.d.ts +3 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +86 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/safe-logger.d.ts +23 -0
- package/dist/utils/safe-logger.d.ts.map +1 -0
- package/dist/utils/safe-logger.js +145 -0
- package/dist/utils/safe-logger.js.map +1 -0
- package/docker-compose.yml +161 -0
- package/jest.config.js +26 -0
- package/package.json +65 -0
- package/scripts/build-multiarch.sh +290 -0
- package/scripts/deploy-raspbian.sh +410 -0
- package/scripts/deploy.sh +322 -0
- package/scripts/gh-deploy.sh +343 -0
- package/scripts/setup-docker-raspbian.sh +530 -0
- package/server.json +8 -0
- package/src/config/config.ts +357 -0
- package/src/config/types.ts +89 -0
- package/src/index.ts +114 -0
- package/src/providers/duck-provider-enhanced.ts +294 -0
- package/src/providers/enhanced-manager.ts +290 -0
- package/src/providers/manager.ts +257 -0
- package/src/providers/provider.ts +207 -0
- package/src/providers/types.ts +78 -0
- package/src/server.ts +603 -0
- package/src/services/approval.ts +225 -0
- package/src/services/cache.ts +79 -0
- package/src/services/conversation.ts +146 -0
- package/src/services/function-bridge.ts +329 -0
- package/src/services/health.ts +107 -0
- package/src/services/mcp-client-manager.ts +362 -0
- package/src/tools/approve-mcp-request.ts +126 -0
- package/src/tools/ask-duck.ts +74 -0
- package/src/tools/chat-duck.ts +82 -0
- package/src/tools/clear-conversations.ts +24 -0
- package/src/tools/compare-ducks.ts +67 -0
- package/src/tools/duck-council.ts +88 -0
- package/src/tools/get-pending-approvals.ts +90 -0
- package/src/tools/list-ducks.ts +65 -0
- package/src/tools/list-models.ts +101 -0
- package/src/tools/mcp-status.ts +117 -0
- package/src/utils/ascii-art.ts +85 -0
- package/src/utils/logger.ts +116 -0
- package/src/utils/safe-logger.ts +165 -0
- package/systemd/mcp-rubber-duck-with-ollama.service +55 -0
- package/systemd/mcp-rubber-duck.service +58 -0
- package/test-functionality.js +147 -0
- package/test-mcp-interface.js +221 -0
- package/tests/ascii-art.test.ts +36 -0
- package/tests/config.test.ts +239 -0
- package/tests/conversation.test.ts +308 -0
- package/tests/mcp-bridge.test.ts +291 -0
- package/tests/providers.test.ts +269 -0
- package/tests/tools/clear-conversations.test.ts +163 -0
- package/tsconfig.json +26 -0
|
@@ -0,0 +1,269 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
|
|
3
|
+
// Mock OpenAI BEFORE importing the provider
|
|
4
|
+
const mockCreate = jest.fn();
|
|
5
|
+
jest.mock('openai', () => {
|
|
6
|
+
const MockOpenAI = jest.fn().mockImplementation(() => ({
|
|
7
|
+
chat: {
|
|
8
|
+
completions: {
|
|
9
|
+
create: mockCreate,
|
|
10
|
+
},
|
|
11
|
+
},
|
|
12
|
+
}));
|
|
13
|
+
return {
|
|
14
|
+
__esModule: true,
|
|
15
|
+
default: MockOpenAI,
|
|
16
|
+
};
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
// Mock config manager and logger
|
|
20
|
+
jest.mock('../src/config/config');
|
|
21
|
+
jest.mock('../src/utils/logger');
|
|
22
|
+
|
|
23
|
+
// NOW import the modules after setting up mocks
|
|
24
|
+
import { DuckProvider } from '../src/providers/provider';
|
|
25
|
+
import { ProviderManager } from '../src/providers/manager';
|
|
26
|
+
import { ConfigManager } from '../src/config/config';
|
|
27
|
+
|
|
28
|
+
describe('DuckProvider', () => {
|
|
29
|
+
let provider: DuckProvider;
|
|
30
|
+
|
|
31
|
+
beforeEach(() => {
|
|
32
|
+
jest.clearAllMocks();
|
|
33
|
+
|
|
34
|
+
// Setup mock response
|
|
35
|
+
mockCreate.mockResolvedValue({
|
|
36
|
+
choices: [{
|
|
37
|
+
message: { content: 'Mocked response' },
|
|
38
|
+
finish_reason: 'stop',
|
|
39
|
+
}],
|
|
40
|
+
usage: {
|
|
41
|
+
prompt_tokens: 10,
|
|
42
|
+
completion_tokens: 20,
|
|
43
|
+
total_tokens: 30,
|
|
44
|
+
},
|
|
45
|
+
model: 'mock-model',
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
provider = new DuckProvider('test', 'Test Duck', {
|
|
49
|
+
apiKey: 'test-key',
|
|
50
|
+
baseURL: 'https://api.test.com/v1',
|
|
51
|
+
model: 'test-model',
|
|
52
|
+
temperature: 0.7,
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
// Override the method on the actual instance since Jest ESM mocking isn't working
|
|
56
|
+
provider['client'].chat.completions.create = mockCreate;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('should create a provider instance', () => {
|
|
60
|
+
expect(provider).toBeDefined();
|
|
61
|
+
expect(provider.name).toBe('test');
|
|
62
|
+
expect(provider.nickname).toBe('Test Duck');
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
it('should get provider info', () => {
|
|
66
|
+
const info = provider.getInfo();
|
|
67
|
+
expect(info).toEqual({
|
|
68
|
+
name: 'test',
|
|
69
|
+
nickname: 'Test Duck',
|
|
70
|
+
model: 'test-model',
|
|
71
|
+
baseURL: 'https://api.test.com/v1',
|
|
72
|
+
hasApiKey: true,
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it('should send chat request', async () => {
|
|
77
|
+
console.log('Starting chat request test');
|
|
78
|
+
console.log('Mock create has been called:', mockCreate.mock.calls.length, 'times');
|
|
79
|
+
|
|
80
|
+
const response = await provider.chat({
|
|
81
|
+
messages: [
|
|
82
|
+
{ role: 'user', content: 'Hello', timestamp: new Date() },
|
|
83
|
+
],
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
console.log('Chat response received:', response);
|
|
87
|
+
expect(response).toBeDefined();
|
|
88
|
+
expect(response.content).toBe('Mocked response');
|
|
89
|
+
expect(response.usage).toBeDefined();
|
|
90
|
+
expect(response.model).toBe('test-model');
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it('should use correct parameters for o1 models', async () => {
|
|
94
|
+
mockCreate.mockClear();
|
|
95
|
+
|
|
96
|
+
const testProvider = new DuckProvider('test', 'Test Duck', {
|
|
97
|
+
apiKey: 'test-key',
|
|
98
|
+
baseURL: 'https://api.test.com/v1',
|
|
99
|
+
model: 'o1',
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
// Override the method on the actual instance since Jest ESM mocking isn't working
|
|
103
|
+
testProvider['client'].chat.completions.create = mockCreate;
|
|
104
|
+
|
|
105
|
+
await testProvider.chat({
|
|
106
|
+
messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
|
|
107
|
+
model: 'o1',
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
expect(mockCreate).toHaveBeenCalledTimes(1);
|
|
111
|
+
const calls = (mockCreate as any).mock.calls;
|
|
112
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
113
|
+
const callParams = calls[0][0];
|
|
114
|
+
|
|
115
|
+
// o1 models should NOT have temperature or token limits
|
|
116
|
+
expect(callParams).not.toHaveProperty('temperature');
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
it('should use correct parameters for GPT-5 models', async () => {
|
|
120
|
+
mockCreate.mockClear();
|
|
121
|
+
|
|
122
|
+
const testProvider = new DuckProvider('test', 'Test Duck', {
|
|
123
|
+
apiKey: 'test-key',
|
|
124
|
+
baseURL: 'https://api.test.com/v1',
|
|
125
|
+
model: 'gpt-5',
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
// Override the method on the actual instance since Jest ESM mocking isn't working
|
|
129
|
+
testProvider['client'].chat.completions.create = mockCreate;
|
|
130
|
+
|
|
131
|
+
await testProvider.chat({
|
|
132
|
+
messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
|
|
133
|
+
model: 'gpt-5',
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
expect(mockCreate).toHaveBeenCalledTimes(1);
|
|
137
|
+
const calls = (mockCreate as any).mock.calls;
|
|
138
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
139
|
+
const callParams = calls[0][0];
|
|
140
|
+
|
|
141
|
+
// GPT-5 models should NOT have temperature or token limits
|
|
142
|
+
expect(callParams).not.toHaveProperty('temperature');
|
|
143
|
+
});
|
|
144
|
+
|
|
145
|
+
it('should use correct parameters for non-o1 models', async () => {
|
|
146
|
+
mockCreate.mockClear();
|
|
147
|
+
|
|
148
|
+
const testProvider = new DuckProvider('test', 'Test Duck', {
|
|
149
|
+
apiKey: 'test-key',
|
|
150
|
+
baseURL: 'https://api.test.com/v1',
|
|
151
|
+
model: 'gpt-4',
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
// Override the method on the actual instance since Jest ESM mocking isn't working
|
|
155
|
+
testProvider['client'].chat.completions.create = mockCreate;
|
|
156
|
+
|
|
157
|
+
await testProvider.chat({
|
|
158
|
+
messages: [{ role: 'user', content: 'Hello', timestamp: new Date() }],
|
|
159
|
+
model: 'gpt-4',
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
expect(mockCreate).toHaveBeenCalledTimes(1);
|
|
163
|
+
const calls = (mockCreate as any).mock.calls;
|
|
164
|
+
expect(calls.length).toBeGreaterThan(0);
|
|
165
|
+
const callParams = calls[0][0];
|
|
166
|
+
|
|
167
|
+
// non-o1 models should have temperature but no token limits
|
|
168
|
+
expect(callParams).toHaveProperty('temperature');
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
describe('ProviderManager', () => {
|
|
173
|
+
let manager: ProviderManager;
|
|
174
|
+
let mockConfigManager: jest.Mocked<ConfigManager>;
|
|
175
|
+
|
|
176
|
+
beforeEach(() => {
|
|
177
|
+
jest.clearAllMocks();
|
|
178
|
+
|
|
179
|
+
// Setup mock response
|
|
180
|
+
mockCreate.mockResolvedValue({
|
|
181
|
+
choices: [{
|
|
182
|
+
message: { content: 'Mocked response' },
|
|
183
|
+
finish_reason: 'stop',
|
|
184
|
+
}],
|
|
185
|
+
usage: {
|
|
186
|
+
prompt_tokens: 10,
|
|
187
|
+
completion_tokens: 20,
|
|
188
|
+
total_tokens: 30,
|
|
189
|
+
},
|
|
190
|
+
model: 'mock-model',
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
mockConfigManager = {
|
|
194
|
+
getConfig: jest.fn().mockReturnValue({
|
|
195
|
+
providers: {
|
|
196
|
+
test1: {
|
|
197
|
+
api_key: 'key1',
|
|
198
|
+
base_url: 'https://api1.test.com/v1',
|
|
199
|
+
default_model: 'model1',
|
|
200
|
+
nickname: 'Duck 1',
|
|
201
|
+
models: ['model1'],
|
|
202
|
+
},
|
|
203
|
+
test2: {
|
|
204
|
+
api_key: 'key2',
|
|
205
|
+
base_url: 'https://api2.test.com/v1',
|
|
206
|
+
default_model: 'model2',
|
|
207
|
+
nickname: 'Duck 2',
|
|
208
|
+
models: ['model2'],
|
|
209
|
+
},
|
|
210
|
+
},
|
|
211
|
+
default_provider: 'test1',
|
|
212
|
+
cache_ttl: 300,
|
|
213
|
+
enable_failover: true,
|
|
214
|
+
default_temperature: 0.7,
|
|
215
|
+
}),
|
|
216
|
+
} as any;
|
|
217
|
+
|
|
218
|
+
manager = new ProviderManager(mockConfigManager);
|
|
219
|
+
|
|
220
|
+
// Override the client method on all providers in the manager
|
|
221
|
+
const provider1 = manager.getProvider('test1');
|
|
222
|
+
const provider2 = manager.getProvider('test2');
|
|
223
|
+
provider1['client'].chat.completions.create = mockCreate;
|
|
224
|
+
provider2['client'].chat.completions.create = mockCreate;
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it('should initialize providers from config', () => {
|
|
228
|
+
const providers = manager.getProviderNames();
|
|
229
|
+
expect(providers).toContain('test1');
|
|
230
|
+
expect(providers).toContain('test2');
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
it('should get a specific provider', () => {
|
|
234
|
+
const provider = manager.getProvider('test1');
|
|
235
|
+
expect(provider).toBeDefined();
|
|
236
|
+
expect(provider.name).toBe('test1');
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
it('should get default provider when no name specified', () => {
|
|
240
|
+
const provider = manager.getProvider();
|
|
241
|
+
expect(provider).toBeDefined();
|
|
242
|
+
expect(provider.name).toBe('test1');
|
|
243
|
+
});
|
|
244
|
+
|
|
245
|
+
it('should throw error for non-existent provider', () => {
|
|
246
|
+
expect(() => manager.getProvider('nonexistent')).toThrow(
|
|
247
|
+
'Duck "nonexistent" not found in the pond'
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('should ask a duck', async () => {
|
|
252
|
+
const response = await manager.askDuck('test1', 'Hello');
|
|
253
|
+
expect(response).toBeDefined();
|
|
254
|
+
expect(response.provider).toBe('test1');
|
|
255
|
+
expect(response.content).toBe('Mocked response');
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it('should compare multiple ducks', async () => {
|
|
259
|
+
const responses = await manager.compareDucks('Hello', ['test1', 'test2']);
|
|
260
|
+
expect(responses).toHaveLength(2);
|
|
261
|
+
expect(responses[0].provider).toBe('test1');
|
|
262
|
+
expect(responses[1].provider).toBe('test2');
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it('should run duck council', async () => {
|
|
266
|
+
const responses = await manager.duckCouncil('Hello');
|
|
267
|
+
expect(responses).toHaveLength(2);
|
|
268
|
+
});
|
|
269
|
+
});
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
import { describe, it, expect, jest, beforeEach } from '@jest/globals';
|
|
2
|
+
import { clearConversationsTool } from '../../src/tools/clear-conversations.js';
|
|
3
|
+
import { ConversationManager } from '../../src/services/conversation.js';
|
|
4
|
+
|
|
5
|
+
// Mock logger to avoid console noise during tests
|
|
6
|
+
jest.mock('../../src/utils/logger');
|
|
7
|
+
|
|
8
|
+
describe('clear_conversations tool', () => {
|
|
9
|
+
let conversationManager: ConversationManager;
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
conversationManager = new ConversationManager();
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
describe('tool execution', () => {
|
|
16
|
+
it('should call ConversationManager.clearAll and return proper response format', () => {
|
|
17
|
+
// Create some conversations first
|
|
18
|
+
conversationManager.createConversation('test-1', 'openai');
|
|
19
|
+
conversationManager.addMessage('test-1', {
|
|
20
|
+
role: 'user' as const,
|
|
21
|
+
content: 'Hello',
|
|
22
|
+
timestamp: new Date(),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
26
|
+
|
|
27
|
+
expect(result).toHaveProperty('content');
|
|
28
|
+
expect(result.content).toHaveLength(1);
|
|
29
|
+
expect(result.content[0]).toHaveProperty('type', 'text');
|
|
30
|
+
expect(result.content[0].text).toContain('🧹 Cleared 1 conversation (1 message)');
|
|
31
|
+
expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
|
|
32
|
+
|
|
33
|
+
// Verify conversations were actually cleared
|
|
34
|
+
expect(conversationManager.getConversation('test-1')).toBeUndefined();
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it('should handle empty state gracefully', () => {
|
|
38
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
39
|
+
|
|
40
|
+
expect(result.content[0].text).toContain('🧹 No conversations to clear - memory is already empty!');
|
|
41
|
+
expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it('should handle multiple conversations correctly', () => {
|
|
45
|
+
// Create multiple conversations with different message counts
|
|
46
|
+
conversationManager.createConversation('test-1', 'openai');
|
|
47
|
+
conversationManager.addMessage('test-1', {
|
|
48
|
+
role: 'user' as const,
|
|
49
|
+
content: 'Hello 1',
|
|
50
|
+
timestamp: new Date(),
|
|
51
|
+
});
|
|
52
|
+
conversationManager.addMessage('test-1', {
|
|
53
|
+
role: 'assistant' as const,
|
|
54
|
+
content: 'Hi 1',
|
|
55
|
+
timestamp: new Date(),
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
conversationManager.createConversation('test-2', 'groq');
|
|
59
|
+
conversationManager.addMessage('test-2', {
|
|
60
|
+
role: 'user' as const,
|
|
61
|
+
content: 'Hello 2',
|
|
62
|
+
timestamp: new Date(),
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
conversationManager.createConversation('test-3', 'gemini');
|
|
66
|
+
// No messages in test-3
|
|
67
|
+
|
|
68
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
69
|
+
|
|
70
|
+
expect(result.content[0].text).toContain('🧹 Cleared 3 conversations (3 messages)');
|
|
71
|
+
expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
|
|
72
|
+
|
|
73
|
+
// Verify all conversations were cleared
|
|
74
|
+
expect(conversationManager.listConversations()).toHaveLength(0);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('should handle singular vs plural correctly', () => {
|
|
78
|
+
// Test single conversation with single message
|
|
79
|
+
conversationManager.createConversation('test-1', 'openai');
|
|
80
|
+
conversationManager.addMessage('test-1', {
|
|
81
|
+
role: 'user' as const,
|
|
82
|
+
content: 'Hello',
|
|
83
|
+
timestamp: new Date(),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
87
|
+
|
|
88
|
+
// Should use singular form
|
|
89
|
+
expect(result.content[0].text).toContain('🧹 Cleared 1 conversation (1 message)');
|
|
90
|
+
expect(result.content[0].text).not.toContain('conversations');
|
|
91
|
+
expect(result.content[0].text).not.toContain('messages)');
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it('should handle args parameter (even though unused)', () => {
|
|
95
|
+
const args = { unused: 'parameter' };
|
|
96
|
+
|
|
97
|
+
conversationManager.createConversation('test-1', 'openai');
|
|
98
|
+
const result = clearConversationsTool(conversationManager, args);
|
|
99
|
+
|
|
100
|
+
expect(result).toHaveProperty('content');
|
|
101
|
+
expect(result.content[0].text).toContain('🧹 Cleared 1 conversation');
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it('should return consistent response structure', () => {
|
|
105
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
106
|
+
|
|
107
|
+
// Verify response structure matches MCP tool format
|
|
108
|
+
expect(result).toEqual({
|
|
109
|
+
content: [
|
|
110
|
+
{
|
|
111
|
+
type: 'text',
|
|
112
|
+
text: expect.stringContaining('🧹'),
|
|
113
|
+
},
|
|
114
|
+
],
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
expect(result.content[0].text).toContain('🦆 All ducks now have a fresh start!');
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
describe('integration with ConversationManager', () => {
|
|
122
|
+
it('should properly clear all conversation state', () => {
|
|
123
|
+
// Create complex scenario
|
|
124
|
+
conversationManager.createConversation('debug-session', 'openai');
|
|
125
|
+
conversationManager.addMessage('debug-session', {
|
|
126
|
+
role: 'user' as const,
|
|
127
|
+
content: 'Help with bug',
|
|
128
|
+
timestamp: new Date(),
|
|
129
|
+
});
|
|
130
|
+
conversationManager.addMessage('debug-session', {
|
|
131
|
+
role: 'assistant' as const,
|
|
132
|
+
content: 'Sure, what\'s the issue?',
|
|
133
|
+
timestamp: new Date(),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
conversationManager.createConversation('code-review', 'groq');
|
|
137
|
+
conversationManager.addMessage('code-review', {
|
|
138
|
+
role: 'user' as const,
|
|
139
|
+
content: 'Review this code',
|
|
140
|
+
timestamp: new Date(),
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
// Switch provider in second conversation
|
|
144
|
+
conversationManager.switchProvider('code-review', 'gemini');
|
|
145
|
+
|
|
146
|
+
// Verify setup
|
|
147
|
+
expect(conversationManager.listConversations()).toHaveLength(2);
|
|
148
|
+
expect(conversationManager.getConversation('debug-session')!.messages).toHaveLength(2);
|
|
149
|
+
expect(conversationManager.getConversation('code-review')!.messages).toHaveLength(2); // 1 + 1 system message
|
|
150
|
+
|
|
151
|
+
// Clear all
|
|
152
|
+
const result = clearConversationsTool(conversationManager, {});
|
|
153
|
+
|
|
154
|
+
// Verify complete cleanup
|
|
155
|
+
expect(conversationManager.listConversations()).toHaveLength(0);
|
|
156
|
+
expect(conversationManager.getConversation('debug-session')).toBeUndefined();
|
|
157
|
+
expect(conversationManager.getConversation('code-review')).toBeUndefined();
|
|
158
|
+
|
|
159
|
+
// Verify counts in response
|
|
160
|
+
expect(result.content[0].text).toContain('🧹 Cleared 2 conversations (4 messages)');
|
|
161
|
+
});
|
|
162
|
+
});
|
|
163
|
+
});
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"outDir": "./dist",
|
|
8
|
+
"rootDir": "./src",
|
|
9
|
+
"strict": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"skipLibCheck": true,
|
|
12
|
+
"forceConsistentCasingInFileNames": true,
|
|
13
|
+
"resolveJsonModule": true,
|
|
14
|
+
"declaration": true,
|
|
15
|
+
"declarationMap": true,
|
|
16
|
+
"sourceMap": true,
|
|
17
|
+
"noUnusedLocals": true,
|
|
18
|
+
"noUnusedParameters": true,
|
|
19
|
+
"noImplicitReturns": true,
|
|
20
|
+
"noFallthroughCasesInSwitch": true,
|
|
21
|
+
"allowSyntheticDefaultImports": true,
|
|
22
|
+
"isolatedModules": true
|
|
23
|
+
},
|
|
24
|
+
"include": ["src/**/*"],
|
|
25
|
+
"exclude": ["node_modules", "dist", "tests"]
|
|
26
|
+
}
|