@cortexmemory/cli 0.27.3 → 0.28.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/dist/commands/db.d.ts.map +1 -1
- package/dist/commands/db.js +18 -6
- package/dist/commands/db.js.map +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +191 -80
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.js +3 -2
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +12 -0
- package/dist/commands/init.js.map +1 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts.map +1 -1
- package/dist/utils/app-template-sync.js +35 -13
- package/dist/utils/app-template-sync.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -1
- package/dist/utils/init/quickstart-setup.js.map +1 -1
- package/package.json +4 -4
- package/templates/basic/.env.local.example +23 -0
- package/templates/basic/README.md +181 -56
- package/templates/basic/package-lock.json +2180 -406
- package/templates/basic/package.json +23 -5
- package/templates/basic/src/__tests__/chat.test.ts +340 -0
- package/templates/basic/src/__tests__/cortex.test.ts +260 -0
- package/templates/basic/src/__tests__/display.test.ts +455 -0
- package/templates/basic/src/__tests__/e2e/fact-extraction.test.ts +498 -0
- package/templates/basic/src/__tests__/e2e/memory-flow.test.ts +355 -0
- package/templates/basic/src/__tests__/e2e/server-e2e.test.ts +414 -0
- package/templates/basic/src/__tests__/helpers/test-utils.ts +345 -0
- package/templates/basic/src/__tests__/integration/chat-flow.test.ts +422 -0
- package/templates/basic/src/__tests__/integration/server.test.ts +441 -0
- package/templates/basic/src/__tests__/llm.test.ts +344 -0
- package/templates/basic/src/chat.ts +300 -0
- package/templates/basic/src/cortex.ts +203 -0
- package/templates/basic/src/display.ts +425 -0
- package/templates/basic/src/index.ts +194 -64
- package/templates/basic/src/llm.ts +214 -0
- package/templates/basic/src/server.ts +280 -0
- package/templates/basic/vitest.config.ts +33 -0
- package/templates/basic/vitest.e2e.config.ts +28 -0
- package/templates/basic/vitest.integration.config.ts +25 -0
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +1 -1
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +61 -19
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +14 -18
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +4 -7
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +95 -23
- package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +339 -0
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +16 -16
- package/templates/vercel-ai-quickstart/app/globals.css +24 -9
- package/templates/vercel-ai-quickstart/app/page.tsx +41 -15
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +3 -1
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +6 -6
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +19 -8
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +46 -16
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
- package/templates/vercel-ai-quickstart/jest.config.js +8 -1
- package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +165 -0
- package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
- package/templates/vercel-ai-quickstart/lib/versions.ts +60 -0
- package/templates/vercel-ai-quickstart/next.config.js +10 -2
- package/templates/vercel-ai-quickstart/package.json +23 -12
- package/templates/vercel-ai-quickstart/test-api.mjs +303 -0
- package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +483 -0
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +40 -40
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +8 -8
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +12 -8
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +4 -1
|
@@ -1,22 +1,40 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "{{PROJECT_NAME}}",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "AI agent with Cortex Memory SDK",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "AI agent with Cortex Memory SDK - demonstrating memory orchestration",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
7
7
|
"dev": "node dev-runner.mjs",
|
|
8
|
+
"start": "tsx --env-file=.env.local src/index.ts",
|
|
9
|
+
"server": "tsx --env-file=.env.local src/server.ts",
|
|
8
10
|
"build": "tsc && npm run build:convex",
|
|
9
11
|
"build:convex": "convex deploy",
|
|
10
|
-
"
|
|
12
|
+
"test": "vitest run",
|
|
13
|
+
"test:watch": "vitest",
|
|
14
|
+
"test:coverage": "vitest run --coverage",
|
|
15
|
+
"test:unit": "vitest run --config vitest.config.ts",
|
|
16
|
+
"test:integration": "vitest run --config vitest.integration.config.ts",
|
|
17
|
+
"test:e2e": "vitest run --config vitest.e2e.config.ts",
|
|
18
|
+
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e"
|
|
11
19
|
},
|
|
12
20
|
"dependencies": {
|
|
13
21
|
"@cortexmemory/sdk": "latest",
|
|
14
22
|
"convex": "^1.31.2",
|
|
15
|
-
"dotenv": "^17.2.3"
|
|
23
|
+
"dotenv": "^17.2.3",
|
|
24
|
+
"hono": "^4.11.3",
|
|
25
|
+
"@hono/node-server": "^1.19.7"
|
|
16
26
|
},
|
|
17
27
|
"devDependencies": {
|
|
18
28
|
"@types/node": "^25.0.3",
|
|
29
|
+
"@vitest/coverage-v8": "^4.0.16",
|
|
19
30
|
"typescript": "^5.9.3",
|
|
20
|
-
"tsx": "^4.21.0"
|
|
31
|
+
"tsx": "^4.21.0",
|
|
32
|
+
"vitest": "^4.0.16"
|
|
33
|
+
},
|
|
34
|
+
"optionalDependencies": {
|
|
35
|
+
"openai": "^6.15.0"
|
|
36
|
+
},
|
|
37
|
+
"engines": {
|
|
38
|
+
"node": ">=20"
|
|
21
39
|
}
|
|
22
40
|
}
|
|
@@ -0,0 +1,340 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for chat.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
// Create mock functions we can control
|
|
8
|
+
const mockRecall = vi.fn();
|
|
9
|
+
const mockRemember = vi.fn();
|
|
10
|
+
const mockFactsList = vi.fn();
|
|
11
|
+
const mockConversationsGet = vi.fn();
|
|
12
|
+
|
|
13
|
+
const mockCortex = {
|
|
14
|
+
memory: {
|
|
15
|
+
recall: mockRecall,
|
|
16
|
+
remember: mockRemember,
|
|
17
|
+
},
|
|
18
|
+
facts: {
|
|
19
|
+
list: mockFactsList,
|
|
20
|
+
},
|
|
21
|
+
conversations: {
|
|
22
|
+
get: mockConversationsGet,
|
|
23
|
+
},
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
// Mock dependencies
|
|
27
|
+
vi.mock("../cortex.js", () => ({
|
|
28
|
+
getCortex: vi.fn(() => mockCortex),
|
|
29
|
+
CONFIG: {
|
|
30
|
+
memorySpaceId: "test-space",
|
|
31
|
+
userId: "test-user",
|
|
32
|
+
userName: "Test User",
|
|
33
|
+
agentId: "test-agent",
|
|
34
|
+
agentName: "Test Agent",
|
|
35
|
+
enableFactExtraction: true,
|
|
36
|
+
enableGraphMemory: false,
|
|
37
|
+
debug: false,
|
|
38
|
+
},
|
|
39
|
+
buildRememberParams: vi.fn().mockResolvedValue({
|
|
40
|
+
memorySpaceId: "test-space",
|
|
41
|
+
conversationId: "conv-123",
|
|
42
|
+
userId: "test-user",
|
|
43
|
+
agentId: "test-agent",
|
|
44
|
+
userMessage: "Hello",
|
|
45
|
+
agentResponse: "Hi there",
|
|
46
|
+
}),
|
|
47
|
+
createLayerObserver: vi.fn().mockReturnValue({
|
|
48
|
+
onOrchestrationStart: vi.fn(),
|
|
49
|
+
onLayerUpdate: vi.fn(),
|
|
50
|
+
onOrchestrationComplete: vi.fn(),
|
|
51
|
+
}),
|
|
52
|
+
}));
|
|
53
|
+
|
|
54
|
+
vi.mock("../display.js", () => ({
|
|
55
|
+
printRecallResults: vi.fn(),
|
|
56
|
+
printOrchestrationComplete: vi.fn(),
|
|
57
|
+
printInfo: vi.fn(),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
vi.mock("../llm.js", () => ({
|
|
61
|
+
generateResponse: vi.fn().mockResolvedValue("Test response"),
|
|
62
|
+
isLLMAvailable: vi.fn().mockReturnValue(false),
|
|
63
|
+
}));
|
|
64
|
+
|
|
65
|
+
describe("chat", () => {
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
vi.clearAllMocks();
|
|
68
|
+
// Reset default mock behaviors
|
|
69
|
+
mockRecall.mockResolvedValue({ memories: [], facts: [] });
|
|
70
|
+
mockRemember.mockResolvedValue({});
|
|
71
|
+
mockFactsList.mockResolvedValue({ facts: [] });
|
|
72
|
+
mockConversationsGet.mockResolvedValue(null);
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
afterEach(() => {
|
|
76
|
+
vi.clearAllMocks();
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe("generateConversationId", () => {
|
|
80
|
+
it("generates unique IDs", async () => {
|
|
81
|
+
const { generateConversationId } = await import("../chat.js");
|
|
82
|
+
|
|
83
|
+
const id1 = generateConversationId();
|
|
84
|
+
const id2 = generateConversationId();
|
|
85
|
+
|
|
86
|
+
expect(id1).not.toBe(id2);
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("generates IDs with correct format", async () => {
|
|
90
|
+
const { generateConversationId } = await import("../chat.js");
|
|
91
|
+
|
|
92
|
+
const id = generateConversationId();
|
|
93
|
+
|
|
94
|
+
expect(id).toMatch(/^conv-\d+-[a-z0-9]+$/);
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("getConversationId", () => {
|
|
99
|
+
it("creates a new conversation ID if none exists", async () => {
|
|
100
|
+
vi.resetModules();
|
|
101
|
+
const { getConversationId } = await import("../chat.js");
|
|
102
|
+
|
|
103
|
+
const id = getConversationId();
|
|
104
|
+
|
|
105
|
+
expect(id).toMatch(/^conv-\d+-[a-z0-9]+$/);
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("returns the same ID on subsequent calls", async () => {
|
|
109
|
+
vi.resetModules();
|
|
110
|
+
const { getConversationId } = await import("../chat.js");
|
|
111
|
+
|
|
112
|
+
const id1 = getConversationId();
|
|
113
|
+
const id2 = getConversationId();
|
|
114
|
+
|
|
115
|
+
expect(id1).toBe(id2);
|
|
116
|
+
});
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
describe("newConversation", () => {
|
|
120
|
+
it("creates a new conversation ID", async () => {
|
|
121
|
+
vi.resetModules();
|
|
122
|
+
const { newConversation, getConversationId } = await import("../chat.js");
|
|
123
|
+
|
|
124
|
+
const oldId = getConversationId();
|
|
125
|
+
const newId = newConversation();
|
|
126
|
+
|
|
127
|
+
expect(newId).not.toBe(oldId);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("prints info message", async () => {
|
|
131
|
+
const { printInfo } = await import("../display.js");
|
|
132
|
+
vi.resetModules();
|
|
133
|
+
const { newConversation } = await import("../chat.js");
|
|
134
|
+
|
|
135
|
+
newConversation();
|
|
136
|
+
|
|
137
|
+
expect(printInfo).toHaveBeenCalledWith(
|
|
138
|
+
expect.stringContaining("Started new conversation"),
|
|
139
|
+
);
|
|
140
|
+
});
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
describe("chat", () => {
|
|
144
|
+
it("calls recall, generate, and remember in sequence", async () => {
|
|
145
|
+
mockRecall.mockResolvedValue({ memories: [], facts: [] });
|
|
146
|
+
mockRemember.mockResolvedValue({});
|
|
147
|
+
|
|
148
|
+
const { generateResponse } = await import("../llm.js");
|
|
149
|
+
vi.mocked(generateResponse).mockResolvedValue("Test response");
|
|
150
|
+
|
|
151
|
+
vi.resetModules();
|
|
152
|
+
const { chat } = await import("../chat.js");
|
|
153
|
+
|
|
154
|
+
const result = await chat("Hello", "conv-123");
|
|
155
|
+
|
|
156
|
+
expect(mockRecall).toHaveBeenCalled();
|
|
157
|
+
expect(generateResponse).toHaveBeenCalledWith("Hello", [], []);
|
|
158
|
+
expect(mockRemember).toHaveBeenCalled();
|
|
159
|
+
expect(result.response).toBe("Test response");
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("returns conversation ID and recall counts", async () => {
|
|
163
|
+
mockRecall.mockResolvedValue({
|
|
164
|
+
memories: [{ content: "mem1" }],
|
|
165
|
+
facts: [{ content: "fact1" }, { content: "fact2" }],
|
|
166
|
+
});
|
|
167
|
+
mockRemember.mockResolvedValue({});
|
|
168
|
+
|
|
169
|
+
vi.resetModules();
|
|
170
|
+
const { chat } = await import("../chat.js");
|
|
171
|
+
|
|
172
|
+
const result = await chat("Hello", "conv-123");
|
|
173
|
+
|
|
174
|
+
expect(result.conversationId).toBe("conv-123");
|
|
175
|
+
expect(result.memoriesRecalled).toBe(1);
|
|
176
|
+
expect(result.factsRecalled).toBe(2);
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
it("handles recall errors gracefully", async () => {
|
|
180
|
+
// Reset the generateResponse mock to ensure it returns expected value
|
|
181
|
+
const { generateResponse } = await import("../llm.js");
|
|
182
|
+
vi.mocked(generateResponse).mockResolvedValue("Test response");
|
|
183
|
+
|
|
184
|
+
// Import chat first before setting up error behavior
|
|
185
|
+
const { chat, generateConversationId } = await import("../chat.js");
|
|
186
|
+
|
|
187
|
+
// Now set up the error
|
|
188
|
+
mockRecall.mockRejectedValueOnce(new Error("Recall failed"));
|
|
189
|
+
mockRemember.mockResolvedValue({});
|
|
190
|
+
|
|
191
|
+
// Should not throw
|
|
192
|
+
const convId = generateConversationId();
|
|
193
|
+
const result = await chat("Hello", convId);
|
|
194
|
+
|
|
195
|
+
expect(result.response).toBe("Test response");
|
|
196
|
+
expect(result.memoriesRecalled).toBe(0);
|
|
197
|
+
expect(result.factsRecalled).toBe(0);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("still returns response if remember fails", async () => {
|
|
201
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
202
|
+
|
|
203
|
+
// Reset the generateResponse mock to ensure it returns expected value
|
|
204
|
+
const { generateResponse } = await import("../llm.js");
|
|
205
|
+
vi.mocked(generateResponse).mockResolvedValue("Test response");
|
|
206
|
+
|
|
207
|
+
// Import chat first
|
|
208
|
+
const { chat, generateConversationId } = await import("../chat.js");
|
|
209
|
+
|
|
210
|
+
// Now set up the behaviors
|
|
211
|
+
mockRecall.mockResolvedValueOnce({ memories: [], facts: [] });
|
|
212
|
+
mockRemember.mockRejectedValueOnce(new Error("Remember failed"));
|
|
213
|
+
|
|
214
|
+
const convId = generateConversationId();
|
|
215
|
+
const result = await chat("Hello", convId);
|
|
216
|
+
|
|
217
|
+
expect(result.response).toBe("Test response");
|
|
218
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
219
|
+
"Failed to store memory:",
|
|
220
|
+
expect.any(Error),
|
|
221
|
+
);
|
|
222
|
+
|
|
223
|
+
consoleSpy.mockRestore();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
it("uses current conversation ID if none provided", async () => {
|
|
227
|
+
mockRecall.mockResolvedValue({ memories: [], facts: [] });
|
|
228
|
+
mockRemember.mockResolvedValue({});
|
|
229
|
+
|
|
230
|
+
vi.resetModules();
|
|
231
|
+
const { chat, getConversationId } = await import("../chat.js");
|
|
232
|
+
|
|
233
|
+
const currentId = getConversationId();
|
|
234
|
+
const result = await chat("Hello");
|
|
235
|
+
|
|
236
|
+
expect(result.conversationId).toBe(currentId);
|
|
237
|
+
});
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
describe("recallMemories", () => {
|
|
241
|
+
it("searches and prints results", async () => {
|
|
242
|
+
mockRecall.mockResolvedValue({
|
|
243
|
+
memories: [{ content: "test memory" }],
|
|
244
|
+
facts: [{ content: "test fact" }],
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
const { printRecallResults } = await import("../display.js");
|
|
248
|
+
|
|
249
|
+
vi.resetModules();
|
|
250
|
+
const { recallMemories } = await import("../chat.js");
|
|
251
|
+
|
|
252
|
+
await recallMemories("test query");
|
|
253
|
+
|
|
254
|
+
expect(mockRecall).toHaveBeenCalledWith(
|
|
255
|
+
expect.objectContaining({
|
|
256
|
+
query: "test query",
|
|
257
|
+
}),
|
|
258
|
+
);
|
|
259
|
+
expect(printRecallResults).toHaveBeenCalled();
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
it("handles errors gracefully", async () => {
|
|
263
|
+
const consoleSpy = vi.spyOn(console, "error").mockImplementation(() => {});
|
|
264
|
+
|
|
265
|
+
mockRecall.mockRejectedValue(new Error("Search failed"));
|
|
266
|
+
|
|
267
|
+
vi.resetModules();
|
|
268
|
+
const { recallMemories } = await import("../chat.js");
|
|
269
|
+
|
|
270
|
+
await recallMemories("test query");
|
|
271
|
+
|
|
272
|
+
expect(consoleSpy).toHaveBeenCalledWith(
|
|
273
|
+
"Recall failed:",
|
|
274
|
+
expect.any(Error),
|
|
275
|
+
);
|
|
276
|
+
|
|
277
|
+
consoleSpy.mockRestore();
|
|
278
|
+
});
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
describe("listFacts", () => {
|
|
282
|
+
it("lists facts and prints results", async () => {
|
|
283
|
+
mockFactsList.mockResolvedValue({
|
|
284
|
+
facts: [{ content: "fact 1" }, { content: "fact 2" }],
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
const { printRecallResults } = await import("../display.js");
|
|
288
|
+
|
|
289
|
+
vi.resetModules();
|
|
290
|
+
const { listFacts } = await import("../chat.js");
|
|
291
|
+
|
|
292
|
+
await listFacts();
|
|
293
|
+
|
|
294
|
+
expect(mockFactsList).toHaveBeenCalled();
|
|
295
|
+
expect(printRecallResults).toHaveBeenCalledWith(
|
|
296
|
+
[],
|
|
297
|
+
expect.arrayContaining([
|
|
298
|
+
expect.objectContaining({ content: "fact 1" }),
|
|
299
|
+
]),
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
describe("getHistory", () => {
|
|
305
|
+
it("prints info when no conversation is active", async () => {
|
|
306
|
+
const { printInfo } = await import("../display.js");
|
|
307
|
+
|
|
308
|
+
vi.resetModules();
|
|
309
|
+
const { getHistory } = await import("../chat.js");
|
|
310
|
+
|
|
311
|
+
// getHistory will call getCortex and use the mock
|
|
312
|
+
await getHistory();
|
|
313
|
+
|
|
314
|
+
// Without a conversation ID, it should print info
|
|
315
|
+
expect(printInfo).toHaveBeenCalled();
|
|
316
|
+
});
|
|
317
|
+
});
|
|
318
|
+
|
|
319
|
+
describe("printConfig", () => {
|
|
320
|
+
it("prints configuration details", async () => {
|
|
321
|
+
const consoleSpy = vi.spyOn(console, "log").mockImplementation(() => {});
|
|
322
|
+
|
|
323
|
+
vi.resetModules();
|
|
324
|
+
const { printConfig } = await import("../chat.js");
|
|
325
|
+
|
|
326
|
+
printConfig();
|
|
327
|
+
|
|
328
|
+
const output = consoleSpy.mock.calls.flat().join("\n");
|
|
329
|
+
expect(output).toContain("Configuration:");
|
|
330
|
+
expect(output).toContain("Memory Space:");
|
|
331
|
+
expect(output).toContain("User:");
|
|
332
|
+
expect(output).toContain("Agent:");
|
|
333
|
+
expect(output).toContain("Fact Extraction:");
|
|
334
|
+
expect(output).toContain("Graph Sync:");
|
|
335
|
+
expect(output).toContain("LLM:");
|
|
336
|
+
|
|
337
|
+
consoleSpy.mockRestore();
|
|
338
|
+
});
|
|
339
|
+
});
|
|
340
|
+
});
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unit tests for cortex.ts
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
|
6
|
+
|
|
7
|
+
// Mock the display module before importing cortex
|
|
8
|
+
vi.mock("../display.js", () => ({
|
|
9
|
+
printLayerUpdate: vi.fn(),
|
|
10
|
+
printOrchestrationStart: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
|
|
13
|
+
describe("cortex", () => {
|
|
14
|
+
const originalEnv = process.env;
|
|
15
|
+
|
|
16
|
+
beforeEach(() => {
|
|
17
|
+
vi.resetModules();
|
|
18
|
+
process.env = { ...originalEnv };
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
afterEach(() => {
|
|
22
|
+
process.env = originalEnv;
|
|
23
|
+
vi.clearAllMocks();
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
describe("CONFIG", () => {
|
|
27
|
+
it("uses default values when environment variables are not set", async () => {
|
|
28
|
+
delete process.env.MEMORY_SPACE_ID;
|
|
29
|
+
delete process.env.USER_ID;
|
|
30
|
+
delete process.env.USER_NAME;
|
|
31
|
+
delete process.env.AGENT_ID;
|
|
32
|
+
delete process.env.AGENT_NAME;
|
|
33
|
+
delete process.env.CORTEX_FACT_EXTRACTION;
|
|
34
|
+
delete process.env.CORTEX_GRAPH_SYNC;
|
|
35
|
+
delete process.env.DEBUG;
|
|
36
|
+
|
|
37
|
+
const { CONFIG } = await import("../cortex.js");
|
|
38
|
+
|
|
39
|
+
expect(CONFIG.memorySpaceId).toBe("basic-demo");
|
|
40
|
+
expect(CONFIG.userId).toBe("demo-user");
|
|
41
|
+
expect(CONFIG.userName).toBe("Demo User");
|
|
42
|
+
expect(CONFIG.agentId).toBe("basic-assistant");
|
|
43
|
+
expect(CONFIG.agentName).toBe("Cortex CLI Assistant");
|
|
44
|
+
expect(CONFIG.enableFactExtraction).toBe(true);
|
|
45
|
+
expect(CONFIG.enableGraphMemory).toBe(false);
|
|
46
|
+
expect(CONFIG.debug).toBe(false);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("uses environment variables when set", async () => {
|
|
50
|
+
process.env.MEMORY_SPACE_ID = "custom-space";
|
|
51
|
+
process.env.USER_ID = "custom-user";
|
|
52
|
+
process.env.USER_NAME = "Custom User";
|
|
53
|
+
process.env.AGENT_ID = "custom-agent";
|
|
54
|
+
process.env.AGENT_NAME = "Custom Agent";
|
|
55
|
+
process.env.CORTEX_FACT_EXTRACTION = "false";
|
|
56
|
+
process.env.CORTEX_GRAPH_SYNC = "true";
|
|
57
|
+
process.env.DEBUG = "true";
|
|
58
|
+
|
|
59
|
+
const { CONFIG } = await import("../cortex.js");
|
|
60
|
+
|
|
61
|
+
expect(CONFIG.memorySpaceId).toBe("custom-space");
|
|
62
|
+
expect(CONFIG.userId).toBe("custom-user");
|
|
63
|
+
expect(CONFIG.userName).toBe("Custom User");
|
|
64
|
+
expect(CONFIG.agentId).toBe("custom-agent");
|
|
65
|
+
expect(CONFIG.agentName).toBe("Custom Agent");
|
|
66
|
+
expect(CONFIG.enableFactExtraction).toBe(false);
|
|
67
|
+
expect(CONFIG.enableGraphMemory).toBe(true);
|
|
68
|
+
expect(CONFIG.debug).toBe(true);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
describe("getCortex", () => {
|
|
73
|
+
it("throws error when CONVEX_URL is not set", async () => {
|
|
74
|
+
delete process.env.CONVEX_URL;
|
|
75
|
+
|
|
76
|
+
const { getCortex } = await import("../cortex.js");
|
|
77
|
+
|
|
78
|
+
expect(() => getCortex()).toThrow("CONVEX_URL environment variable is required");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("creates Cortex client when CONVEX_URL is set", async () => {
|
|
82
|
+
process.env.CONVEX_URL = "https://test.convex.cloud";
|
|
83
|
+
|
|
84
|
+
// Mock Cortex SDK
|
|
85
|
+
vi.doMock("@cortexmemory/sdk", () => ({
|
|
86
|
+
Cortex: vi.fn().mockImplementation(() => ({
|
|
87
|
+
memory: {},
|
|
88
|
+
close: vi.fn(),
|
|
89
|
+
})),
|
|
90
|
+
}));
|
|
91
|
+
|
|
92
|
+
const { getCortex } = await import("../cortex.js");
|
|
93
|
+
const cortex = getCortex();
|
|
94
|
+
|
|
95
|
+
expect(cortex).toBeDefined();
|
|
96
|
+
expect(cortex.memory).toBeDefined();
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it("returns same instance on subsequent calls (singleton)", async () => {
|
|
100
|
+
process.env.CONVEX_URL = "https://test.convex.cloud";
|
|
101
|
+
|
|
102
|
+
vi.doMock("@cortexmemory/sdk", () => ({
|
|
103
|
+
Cortex: vi.fn().mockImplementation(() => ({
|
|
104
|
+
memory: {},
|
|
105
|
+
close: vi.fn(),
|
|
106
|
+
})),
|
|
107
|
+
}));
|
|
108
|
+
|
|
109
|
+
const { getCortex } = await import("../cortex.js");
|
|
110
|
+
|
|
111
|
+
const instance1 = getCortex();
|
|
112
|
+
const instance2 = getCortex();
|
|
113
|
+
|
|
114
|
+
expect(instance1).toBe(instance2);
|
|
115
|
+
});
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
describe("closeCortex", () => {
|
|
119
|
+
it("closes the client and resets singleton", async () => {
|
|
120
|
+
process.env.CONVEX_URL = "https://test.convex.cloud";
|
|
121
|
+
|
|
122
|
+
const closeMock = vi.fn();
|
|
123
|
+
vi.doMock("@cortexmemory/sdk", () => ({
|
|
124
|
+
Cortex: vi.fn().mockImplementation(() => ({
|
|
125
|
+
memory: {},
|
|
126
|
+
close: closeMock,
|
|
127
|
+
})),
|
|
128
|
+
}));
|
|
129
|
+
|
|
130
|
+
const { getCortex, closeCortex } = await import("../cortex.js");
|
|
131
|
+
|
|
132
|
+
getCortex(); // Create instance
|
|
133
|
+
closeCortex();
|
|
134
|
+
|
|
135
|
+
expect(closeMock).toHaveBeenCalled();
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("does nothing when no client exists", async () => {
|
|
139
|
+
const { closeCortex } = await import("../cortex.js");
|
|
140
|
+
|
|
141
|
+
// Should not throw
|
|
142
|
+
expect(() => closeCortex()).not.toThrow();
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
describe("createLayerObserver", () => {
|
|
147
|
+
it("returns an observer with required methods", async () => {
|
|
148
|
+
const { createLayerObserver } = await import("../cortex.js");
|
|
149
|
+
|
|
150
|
+
const observer = createLayerObserver();
|
|
151
|
+
|
|
152
|
+
expect(observer).toHaveProperty("onOrchestrationStart");
|
|
153
|
+
expect(observer).toHaveProperty("onLayerUpdate");
|
|
154
|
+
expect(observer).toHaveProperty("onOrchestrationComplete");
|
|
155
|
+
expect(typeof observer.onOrchestrationStart).toBe("function");
|
|
156
|
+
expect(typeof observer.onLayerUpdate).toBe("function");
|
|
157
|
+
expect(typeof observer.onOrchestrationComplete).toBe("function");
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("calls printOrchestrationStart on orchestration start", async () => {
|
|
161
|
+
const { printOrchestrationStart } = await import("../display.js");
|
|
162
|
+
const { createLayerObserver } = await import("../cortex.js");
|
|
163
|
+
|
|
164
|
+
const observer = createLayerObserver();
|
|
165
|
+
observer.onOrchestrationStart("test-id");
|
|
166
|
+
|
|
167
|
+
expect(printOrchestrationStart).toHaveBeenCalledWith("test-id");
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
it("calls printLayerUpdate on layer update", async () => {
|
|
171
|
+
const { printLayerUpdate } = await import("../display.js");
|
|
172
|
+
const { createLayerObserver } = await import("../cortex.js");
|
|
173
|
+
|
|
174
|
+
const observer = createLayerObserver();
|
|
175
|
+
const event = {
|
|
176
|
+
layer: "facts",
|
|
177
|
+
status: "complete" as const,
|
|
178
|
+
timestamp: Date.now(),
|
|
179
|
+
latencyMs: 100,
|
|
180
|
+
};
|
|
181
|
+
observer.onLayerUpdate(event);
|
|
182
|
+
|
|
183
|
+
expect(printLayerUpdate).toHaveBeenCalledWith(event);
|
|
184
|
+
});
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
describe("getEmbeddingProvider", () => {
|
|
188
|
+
it("returns undefined when OPENAI_API_KEY is not set", async () => {
|
|
189
|
+
delete process.env.OPENAI_API_KEY;
|
|
190
|
+
|
|
191
|
+
const { getEmbeddingProvider } = await import("../cortex.js");
|
|
192
|
+
const provider = await getEmbeddingProvider();
|
|
193
|
+
|
|
194
|
+
expect(provider).toBeUndefined();
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("returns embedding function when OPENAI_API_KEY is set", async () => {
|
|
198
|
+
process.env.OPENAI_API_KEY = "sk-test-key";
|
|
199
|
+
|
|
200
|
+
// Mock OpenAI
|
|
201
|
+
vi.doMock("openai", () => ({
|
|
202
|
+
default: vi.fn().mockImplementation(() => ({
|
|
203
|
+
embeddings: {
|
|
204
|
+
create: vi.fn().mockResolvedValue({
|
|
205
|
+
data: [{ embedding: new Array(1536).fill(0.1) }],
|
|
206
|
+
}),
|
|
207
|
+
},
|
|
208
|
+
})),
|
|
209
|
+
}));
|
|
210
|
+
|
|
211
|
+
const { getEmbeddingProvider } = await import("../cortex.js");
|
|
212
|
+
const provider = await getEmbeddingProvider();
|
|
213
|
+
|
|
214
|
+
expect(provider).toBeDefined();
|
|
215
|
+
expect(typeof provider).toBe("function");
|
|
216
|
+
});
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
describe("buildRememberParams", () => {
|
|
220
|
+
it("builds params with CONFIG values", async () => {
|
|
221
|
+
delete process.env.OPENAI_API_KEY;
|
|
222
|
+
|
|
223
|
+
const { buildRememberParams, CONFIG } = await import("../cortex.js");
|
|
224
|
+
|
|
225
|
+
const params = await buildRememberParams({
|
|
226
|
+
userMessage: "Hello",
|
|
227
|
+
agentResponse: "Hi there",
|
|
228
|
+
conversationId: "conv-123",
|
|
229
|
+
});
|
|
230
|
+
|
|
231
|
+
expect(params.memorySpaceId).toBe(CONFIG.memorySpaceId);
|
|
232
|
+
expect(params.userId).toBe(CONFIG.userId);
|
|
233
|
+
expect(params.userName).toBe(CONFIG.userName);
|
|
234
|
+
expect(params.agentId).toBe(CONFIG.agentId);
|
|
235
|
+
expect(params.userMessage).toBe("Hello");
|
|
236
|
+
expect(params.agentResponse).toBe("Hi there");
|
|
237
|
+
expect(params.conversationId).toBe("conv-123");
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("includes belief revision when fact extraction is enabled", async () => {
|
|
241
|
+
delete process.env.OPENAI_API_KEY;
|
|
242
|
+
process.env.CORTEX_FACT_EXTRACTION = "true";
|
|
243
|
+
|
|
244
|
+
const { buildRememberParams } = await import("../cortex.js");
|
|
245
|
+
|
|
246
|
+
const params = await buildRememberParams({
|
|
247
|
+
userMessage: "Hello",
|
|
248
|
+
agentResponse: "Hi",
|
|
249
|
+
conversationId: "conv-123",
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
expect(params.extractFacts).toBe(true);
|
|
253
|
+
expect(params.beliefRevision).toEqual({
|
|
254
|
+
enabled: true,
|
|
255
|
+
slotMatching: true,
|
|
256
|
+
llmResolution: false, // No OpenAI key
|
|
257
|
+
});
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
});
|