@cortexmemory/cli 0.27.4 → 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 +74 -34
- 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 +3 -1
- 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 +6 -9
- 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 +28 -11
- package/templates/vercel-ai-quickstart/app/api/chat-v6/route.ts +19 -13
- 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 +25 -13
- 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 +41 -14
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +10 -5
- package/templates/vercel-ai-quickstart/lib/agents/memory-agent.ts +3 -3
- package/templates/vercel-ai-quickstart/lib/password.ts +5 -5
- package/templates/vercel-ai-quickstart/next.config.js +10 -2
- package/templates/vercel-ai-quickstart/package.json +18 -11
- package/templates/vercel-ai-quickstart/test-api.mjs +131 -100
- package/templates/vercel-ai-quickstart/tests/e2e/chat-memory-flow.test.ts +73 -44
- 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
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cortex Memory - HTTP Server Mode
|
|
3
|
+
*
|
|
4
|
+
* REST API server for demonstrating Cortex Memory SDK.
|
|
5
|
+
* Useful for testing with tools like curl, Postman, or integrating with other apps.
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* npm run server
|
|
9
|
+
*
|
|
10
|
+
* Endpoints:
|
|
11
|
+
* POST /chat Chat and store memory
|
|
12
|
+
* GET /recall Search memories
|
|
13
|
+
* GET /facts List stored facts
|
|
14
|
+
* GET /history/:id Get conversation history
|
|
15
|
+
* GET /health Health check
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
import { serve } from "@hono/node-server";
|
|
19
|
+
import { Hono } from "hono";
|
|
20
|
+
import { cors } from "hono/cors";
|
|
21
|
+
import { logger } from "hono/logger";
|
|
22
|
+
import { closeCortex, CONFIG } from "./cortex.js";
|
|
23
|
+
import {
|
|
24
|
+
chat,
|
|
25
|
+
recallMemories,
|
|
26
|
+
listFacts,
|
|
27
|
+
generateConversationId,
|
|
28
|
+
} from "./chat.js";
|
|
29
|
+
import { printWelcome, printInfo, printError } from "./display.js";
|
|
30
|
+
|
|
31
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
32
|
+
// Server Setup
|
|
33
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
34
|
+
|
|
35
|
+
const app = new Hono();
|
|
36
|
+
|
|
37
|
+
// Middleware
|
|
38
|
+
app.use("*", cors());
|
|
39
|
+
app.use("*", logger());
|
|
40
|
+
|
|
41
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
42
|
+
// Routes
|
|
43
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Health check
|
|
47
|
+
*/
|
|
48
|
+
app.get("/health", (c) => {
|
|
49
|
+
return c.json({
|
|
50
|
+
status: "ok",
|
|
51
|
+
memorySpaceId: CONFIG.memorySpaceId,
|
|
52
|
+
agentId: CONFIG.agentId,
|
|
53
|
+
features: {
|
|
54
|
+
factExtraction: CONFIG.enableFactExtraction,
|
|
55
|
+
graphSync: CONFIG.enableGraphMemory,
|
|
56
|
+
llm: !!process.env.OPENAI_API_KEY,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Chat endpoint
|
|
63
|
+
*
|
|
64
|
+
* POST /chat
|
|
65
|
+
* Body: { message: string, conversationId?: string }
|
|
66
|
+
*/
|
|
67
|
+
app.post("/chat", async (c) => {
|
|
68
|
+
try {
|
|
69
|
+
const body = await c.req.json();
|
|
70
|
+
const { message, conversationId } = body;
|
|
71
|
+
|
|
72
|
+
if (!message || typeof message !== "string") {
|
|
73
|
+
return c.json({ error: "message is required" }, 400);
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const convId = conversationId || generateConversationId();
|
|
77
|
+
|
|
78
|
+
console.log(`\n[Chat] User: ${message.slice(0, 50)}${message.length > 50 ? "..." : ""}`);
|
|
79
|
+
|
|
80
|
+
const result = await chat(message, convId);
|
|
81
|
+
|
|
82
|
+
console.log(`[Chat] Response sent (${result.memoriesRecalled} memories, ${result.factsRecalled} facts recalled)\n`);
|
|
83
|
+
|
|
84
|
+
return c.json({
|
|
85
|
+
response: result.response,
|
|
86
|
+
conversationId: result.conversationId,
|
|
87
|
+
memoriesRecalled: result.memoriesRecalled,
|
|
88
|
+
factsRecalled: result.factsRecalled,
|
|
89
|
+
});
|
|
90
|
+
} catch (error) {
|
|
91
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
92
|
+
printError("Chat failed", error instanceof Error ? error : undefined);
|
|
93
|
+
return c.json({ error: message }, 500);
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* Recall endpoint
|
|
99
|
+
*
|
|
100
|
+
* GET /recall?query=<query>
|
|
101
|
+
*/
|
|
102
|
+
app.get("/recall", async (c) => {
|
|
103
|
+
try {
|
|
104
|
+
const query = c.req.query("query");
|
|
105
|
+
|
|
106
|
+
if (!query) {
|
|
107
|
+
return c.json({ error: "query parameter is required" }, 400);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
console.log(`\n[Recall] Query: ${query}`);
|
|
111
|
+
|
|
112
|
+
// Use the internal recall function but capture results
|
|
113
|
+
const { getCortex } = await import("./cortex.js");
|
|
114
|
+
const cortex = getCortex();
|
|
115
|
+
|
|
116
|
+
const result = await cortex.memory.recall({
|
|
117
|
+
memorySpaceId: CONFIG.memorySpaceId,
|
|
118
|
+
query,
|
|
119
|
+
limit: 10,
|
|
120
|
+
sources: {
|
|
121
|
+
vector: true,
|
|
122
|
+
facts: true,
|
|
123
|
+
graph: CONFIG.enableGraphMemory,
|
|
124
|
+
},
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
console.log(`[Recall] Found ${result.memories?.length || 0} memories, ${result.facts?.length || 0} facts\n`);
|
|
128
|
+
|
|
129
|
+
return c.json({
|
|
130
|
+
memories: result.memories || [],
|
|
131
|
+
facts: result.facts || [],
|
|
132
|
+
query,
|
|
133
|
+
});
|
|
134
|
+
} catch (error) {
|
|
135
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
136
|
+
return c.json({ error: message }, 500);
|
|
137
|
+
}
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
/**
|
|
141
|
+
* Facts endpoint
|
|
142
|
+
*
|
|
143
|
+
* GET /facts
|
|
144
|
+
*/
|
|
145
|
+
app.get("/facts", async (c) => {
|
|
146
|
+
try {
|
|
147
|
+
const { getCortex } = await import("./cortex.js");
|
|
148
|
+
const cortex = getCortex();
|
|
149
|
+
|
|
150
|
+
const result = await cortex.facts.list({
|
|
151
|
+
memorySpaceId: CONFIG.memorySpaceId,
|
|
152
|
+
limit: 50,
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
const facts = result.facts || result || [];
|
|
156
|
+
|
|
157
|
+
return c.json({ facts, count: facts.length });
|
|
158
|
+
} catch (error) {
|
|
159
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
160
|
+
return c.json({ error: message }, 500);
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Conversation history endpoint
|
|
166
|
+
*
|
|
167
|
+
* GET /history/:conversationId
|
|
168
|
+
*/
|
|
169
|
+
app.get("/history/:conversationId", async (c) => {
|
|
170
|
+
try {
|
|
171
|
+
const conversationId = c.req.param("conversationId");
|
|
172
|
+
|
|
173
|
+
const { getCortex } = await import("./cortex.js");
|
|
174
|
+
const cortex = getCortex();
|
|
175
|
+
|
|
176
|
+
const conversation = await cortex.conversations.get(conversationId);
|
|
177
|
+
|
|
178
|
+
if (!conversation) {
|
|
179
|
+
return c.json({ error: "Conversation not found" }, 404);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
return c.json({
|
|
183
|
+
conversationId,
|
|
184
|
+
messages: conversation.messages || [],
|
|
185
|
+
messageCount: conversation.messages?.length || 0,
|
|
186
|
+
});
|
|
187
|
+
} catch (error) {
|
|
188
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
189
|
+
return c.json({ error: message }, 500);
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Root endpoint - API docs
|
|
195
|
+
*/
|
|
196
|
+
app.get("/", (c) => {
|
|
197
|
+
return c.json({
|
|
198
|
+
name: "Cortex Memory - Basic Demo API",
|
|
199
|
+
version: "1.0.0",
|
|
200
|
+
endpoints: {
|
|
201
|
+
"POST /chat": {
|
|
202
|
+
description: "Chat and store memory",
|
|
203
|
+
body: { message: "string", conversationId: "string (optional)" },
|
|
204
|
+
},
|
|
205
|
+
"GET /recall": {
|
|
206
|
+
description: "Search memories",
|
|
207
|
+
query: { query: "string" },
|
|
208
|
+
},
|
|
209
|
+
"GET /facts": {
|
|
210
|
+
description: "List all stored facts",
|
|
211
|
+
},
|
|
212
|
+
"GET /history/:id": {
|
|
213
|
+
description: "Get conversation history",
|
|
214
|
+
},
|
|
215
|
+
"GET /health": {
|
|
216
|
+
description: "Health check",
|
|
217
|
+
},
|
|
218
|
+
},
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
223
|
+
// Server Start
|
|
224
|
+
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
225
|
+
|
|
226
|
+
const PORT = parseInt(process.env.PORT || "3001", 10);
|
|
227
|
+
|
|
228
|
+
async function main(): Promise<void> {
|
|
229
|
+
// Check for required environment
|
|
230
|
+
if (!process.env.CONVEX_URL) {
|
|
231
|
+
printError(
|
|
232
|
+
"CONVEX_URL is required. Set it in .env.local or run: cortex init",
|
|
233
|
+
);
|
|
234
|
+
process.exit(1);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Print welcome
|
|
238
|
+
printWelcome("server");
|
|
239
|
+
|
|
240
|
+
// Start server
|
|
241
|
+
console.log(`🚀 Server starting on http://localhost:${PORT}`);
|
|
242
|
+
console.log("");
|
|
243
|
+
console.log("Endpoints:");
|
|
244
|
+
console.log(` POST http://localhost:${PORT}/chat`);
|
|
245
|
+
console.log(` GET http://localhost:${PORT}/recall?query=...`);
|
|
246
|
+
console.log(` GET http://localhost:${PORT}/facts`);
|
|
247
|
+
console.log(` GET http://localhost:${PORT}/history/:id`);
|
|
248
|
+
console.log(` GET http://localhost:${PORT}/health`);
|
|
249
|
+
console.log("");
|
|
250
|
+
console.log("Example:");
|
|
251
|
+
console.log(` curl -X POST http://localhost:${PORT}/chat \\`);
|
|
252
|
+
console.log(' -H "Content-Type: application/json" \\');
|
|
253
|
+
console.log(' -d \'{"message": "My name is Alex"}\'');
|
|
254
|
+
console.log("");
|
|
255
|
+
|
|
256
|
+
serve({
|
|
257
|
+
fetch: app.fetch,
|
|
258
|
+
port: PORT,
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
printInfo(`Server running on port ${PORT}`);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
// Handle cleanup
|
|
265
|
+
process.on("SIGINT", () => {
|
|
266
|
+
console.log("\n\nShutting down...");
|
|
267
|
+
closeCortex();
|
|
268
|
+
process.exit(0);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
process.on("SIGTERM", () => {
|
|
272
|
+
closeCortex();
|
|
273
|
+
process.exit(0);
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
// Run
|
|
277
|
+
main().catch((error) => {
|
|
278
|
+
printError("Fatal error", error instanceof Error ? error : undefined);
|
|
279
|
+
process.exit(1);
|
|
280
|
+
});
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: "node",
|
|
7
|
+
include: [
|
|
8
|
+
"src/**/*.test.ts",
|
|
9
|
+
"src/__tests__/**/*.test.ts",
|
|
10
|
+
],
|
|
11
|
+
// Unit tests run fast
|
|
12
|
+
testTimeout: 10000,
|
|
13
|
+
coverage: {
|
|
14
|
+
provider: "v8",
|
|
15
|
+
reporter: ["text", "json", "html"],
|
|
16
|
+
include: ["src/**/*.ts"],
|
|
17
|
+
exclude: [
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/__tests__/**",
|
|
20
|
+
"src/index.ts", // Entry point, tested via integration
|
|
21
|
+
"src/server.ts", // Entry point, tested via integration
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
clearMocks: true,
|
|
25
|
+
restoreMocks: true,
|
|
26
|
+
// Separate pools for different test types
|
|
27
|
+
poolOptions: {
|
|
28
|
+
forks: {
|
|
29
|
+
singleFork: true,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for E2E tests.
|
|
5
|
+
* These tests require real Convex backend and optionally OpenAI API key.
|
|
6
|
+
*
|
|
7
|
+
* Run with:
|
|
8
|
+
* CONVEX_URL=<url> npm run test:e2e
|
|
9
|
+
* CONVEX_URL=<url> OPENAI_API_KEY=<key> npm run test:e2e
|
|
10
|
+
*/
|
|
11
|
+
export default defineConfig({
|
|
12
|
+
test: {
|
|
13
|
+
globals: true,
|
|
14
|
+
environment: "node",
|
|
15
|
+
include: ["src/__tests__/e2e/**/*.test.ts"],
|
|
16
|
+
testTimeout: 180000, // 3 minutes for E2E tests (fact extraction takes time)
|
|
17
|
+
hookTimeout: 60000, // 1 minute for setup/teardown
|
|
18
|
+
clearMocks: true,
|
|
19
|
+
restoreMocks: true,
|
|
20
|
+
// Run E2E tests sequentially to avoid rate limits
|
|
21
|
+
pool: "forks",
|
|
22
|
+
poolOptions: {
|
|
23
|
+
forks: {
|
|
24
|
+
singleFork: true,
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { defineConfig } from "vitest/config";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Configuration for integration tests.
|
|
5
|
+
* These tests use mocked SDK but test real component interactions.
|
|
6
|
+
*/
|
|
7
|
+
export default defineConfig({
|
|
8
|
+
test: {
|
|
9
|
+
globals: true,
|
|
10
|
+
environment: "node",
|
|
11
|
+
include: ["src/__tests__/integration/**/*.test.ts"],
|
|
12
|
+
testTimeout: 30000, // 30 seconds for integration tests
|
|
13
|
+
coverage: {
|
|
14
|
+
provider: "v8",
|
|
15
|
+
reporter: ["text", "json", "html"],
|
|
16
|
+
include: ["src/**/*.ts"],
|
|
17
|
+
exclude: [
|
|
18
|
+
"src/**/*.test.ts",
|
|
19
|
+
"src/__tests__/**",
|
|
20
|
+
],
|
|
21
|
+
},
|
|
22
|
+
clearMocks: true,
|
|
23
|
+
restoreMocks: true,
|
|
24
|
+
},
|
|
25
|
+
});
|
|
@@ -12,7 +12,7 @@ import { verifyPassword, generateSessionToken } from "@/lib/password";
|
|
|
12
12
|
* Returns validated credentials or null if invalid.
|
|
13
13
|
*/
|
|
14
14
|
function validateLoginBody(
|
|
15
|
-
body: unknown
|
|
15
|
+
body: unknown,
|
|
16
16
|
): { username: string; password: string } | null {
|
|
17
17
|
if (typeof body !== "object" || body === null) {
|
|
18
18
|
return null;
|
|
@@ -65,7 +65,7 @@ export async function POST(req: Request) {
|
|
|
65
65
|
if (!credentials) {
|
|
66
66
|
return Response.json(
|
|
67
67
|
{ error: "Username and password are required" },
|
|
68
|
-
{ status: 400 }
|
|
68
|
+
{ status: 400 },
|
|
69
69
|
);
|
|
70
70
|
}
|
|
71
71
|
|
|
@@ -79,7 +79,7 @@ export async function POST(req: Request) {
|
|
|
79
79
|
if (!user) {
|
|
80
80
|
return Response.json(
|
|
81
81
|
{ error: "Invalid username or password" },
|
|
82
|
-
{ status: 401 }
|
|
82
|
+
{ status: 401 },
|
|
83
83
|
);
|
|
84
84
|
}
|
|
85
85
|
|
|
@@ -88,7 +88,7 @@ export async function POST(req: Request) {
|
|
|
88
88
|
if (!storedHash) {
|
|
89
89
|
return Response.json(
|
|
90
90
|
{ error: "Invalid username or password" },
|
|
91
|
-
{ status: 401 }
|
|
91
|
+
{ status: 401 },
|
|
92
92
|
);
|
|
93
93
|
}
|
|
94
94
|
|
|
@@ -96,7 +96,7 @@ export async function POST(req: Request) {
|
|
|
96
96
|
if (!isValid) {
|
|
97
97
|
return Response.json(
|
|
98
98
|
{ error: "Invalid username or password" },
|
|
99
|
-
{ status: 401 }
|
|
99
|
+
{ status: 401 },
|
|
100
100
|
);
|
|
101
101
|
}
|
|
102
102
|
|
|
@@ -120,9 +120,6 @@ export async function POST(req: Request) {
|
|
|
120
120
|
// Log sanitized error to prevent log injection
|
|
121
121
|
console.error("[Login Error]", getSafeErrorMessage(error));
|
|
122
122
|
|
|
123
|
-
return Response.json(
|
|
124
|
-
{ error: "Failed to authenticate" },
|
|
125
|
-
{ status: 500 }
|
|
126
|
-
);
|
|
123
|
+
return Response.json({ error: "Failed to authenticate" }, { status: 500 });
|
|
127
124
|
}
|
|
128
125
|
}
|
|
@@ -14,39 +14,38 @@ export async function POST(req: Request) {
|
|
|
14
14
|
|
|
15
15
|
// Validate input
|
|
16
16
|
if (!username || typeof username !== "string") {
|
|
17
|
-
return Response.json(
|
|
18
|
-
{ error: "Username is required" },
|
|
19
|
-
{ status: 400 }
|
|
20
|
-
);
|
|
17
|
+
return Response.json({ error: "Username is required" }, { status: 400 });
|
|
21
18
|
}
|
|
22
19
|
|
|
23
20
|
if (!password || typeof password !== "string") {
|
|
24
|
-
return Response.json(
|
|
25
|
-
{ error: "Password is required" },
|
|
26
|
-
{ status: 400 }
|
|
27
|
-
);
|
|
21
|
+
return Response.json({ error: "Password is required" }, { status: 400 });
|
|
28
22
|
}
|
|
29
23
|
|
|
30
24
|
if (username.length < 2) {
|
|
31
25
|
return Response.json(
|
|
32
26
|
{ error: "Username must be at least 2 characters" },
|
|
33
|
-
{ status: 400 }
|
|
27
|
+
{ status: 400 },
|
|
34
28
|
);
|
|
35
29
|
}
|
|
36
30
|
|
|
37
31
|
if (password.length < 4) {
|
|
38
32
|
return Response.json(
|
|
39
33
|
{ error: "Password must be at least 4 characters" },
|
|
40
|
-
{ status: 400 }
|
|
34
|
+
{ status: 400 },
|
|
41
35
|
);
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
// Sanitize username (alphanumeric, underscore, hyphen only)
|
|
45
|
-
const sanitizedUsername = username
|
|
39
|
+
const sanitizedUsername = username
|
|
40
|
+
.toLowerCase()
|
|
41
|
+
.replace(/[^a-z0-9_-]/g, "");
|
|
46
42
|
if (sanitizedUsername !== username.toLowerCase()) {
|
|
47
43
|
return Response.json(
|
|
48
|
-
{
|
|
49
|
-
|
|
44
|
+
{
|
|
45
|
+
error:
|
|
46
|
+
"Username can only contain letters, numbers, underscores, and hyphens",
|
|
47
|
+
},
|
|
48
|
+
{ status: 400 },
|
|
50
49
|
);
|
|
51
50
|
}
|
|
52
51
|
|
|
@@ -57,7 +56,7 @@ export async function POST(req: Request) {
|
|
|
57
56
|
if (existingUser) {
|
|
58
57
|
return Response.json(
|
|
59
58
|
{ error: "Username already taken" },
|
|
60
|
-
{ status: 409 }
|
|
59
|
+
{ status: 409 },
|
|
61
60
|
);
|
|
62
61
|
}
|
|
63
62
|
|
|
@@ -86,9 +85,6 @@ export async function POST(req: Request) {
|
|
|
86
85
|
} catch (error) {
|
|
87
86
|
console.error("[Register Error]", error);
|
|
88
87
|
|
|
89
|
-
return Response.json(
|
|
90
|
-
{ error: "Failed to register user" },
|
|
91
|
-
{ status: 500 }
|
|
92
|
-
);
|
|
88
|
+
return Response.json({ error: "Failed to register user" }, { status: 500 });
|
|
93
89
|
}
|
|
94
90
|
}
|
|
@@ -16,16 +16,13 @@ export async function POST(req: Request) {
|
|
|
16
16
|
const { password } = body;
|
|
17
17
|
|
|
18
18
|
if (!password || typeof password !== "string") {
|
|
19
|
-
return Response.json(
|
|
20
|
-
{ error: "Password is required" },
|
|
21
|
-
{ status: 400 }
|
|
22
|
-
);
|
|
19
|
+
return Response.json({ error: "Password is required" }, { status: 400 });
|
|
23
20
|
}
|
|
24
21
|
|
|
25
22
|
if (password.length < 4) {
|
|
26
23
|
return Response.json(
|
|
27
24
|
{ error: "Password must be at least 4 characters" },
|
|
28
|
-
{ status: 400 }
|
|
25
|
+
{ status: 400 },
|
|
29
26
|
);
|
|
30
27
|
}
|
|
31
28
|
|
|
@@ -36,7 +33,7 @@ export async function POST(req: Request) {
|
|
|
36
33
|
if (existingHash !== null) {
|
|
37
34
|
return Response.json(
|
|
38
35
|
{ error: "Admin already configured" },
|
|
39
|
-
{ status: 409 }
|
|
36
|
+
{ status: 409 },
|
|
40
37
|
);
|
|
41
38
|
}
|
|
42
39
|
|
|
@@ -53,7 +50,7 @@ export async function POST(req: Request) {
|
|
|
53
50
|
|
|
54
51
|
return Response.json(
|
|
55
52
|
{ error: "Failed to configure admin password" },
|
|
56
|
-
{ status: 500 }
|
|
53
|
+
{ status: 500 },
|
|
57
54
|
);
|
|
58
55
|
}
|
|
59
56
|
}
|
|
@@ -164,7 +164,10 @@ function normalizeMessages(messages: unknown[]): unknown[] {
|
|
|
164
164
|
/**
|
|
165
165
|
* Extract text from a message (handles both content string and parts array)
|
|
166
166
|
*/
|
|
167
|
-
function getMessageText(message: {
|
|
167
|
+
function getMessageText(message: {
|
|
168
|
+
content?: string;
|
|
169
|
+
parts?: Array<{ type: string; text?: string }>;
|
|
170
|
+
}): string {
|
|
168
171
|
if (typeof message.content === "string") {
|
|
169
172
|
return message.content;
|
|
170
173
|
}
|
|
@@ -180,7 +183,12 @@ function getMessageText(message: { content?: string; parts?: Array<{ type: strin
|
|
|
180
183
|
export async function POST(req: Request) {
|
|
181
184
|
try {
|
|
182
185
|
const body = await req.json();
|
|
183
|
-
const {
|
|
186
|
+
const {
|
|
187
|
+
messages,
|
|
188
|
+
memorySpaceId,
|
|
189
|
+
userId,
|
|
190
|
+
conversationId: providedConversationId,
|
|
191
|
+
} = body;
|
|
184
192
|
|
|
185
193
|
// Validate messages array exists
|
|
186
194
|
if (!messages || !Array.isArray(messages)) {
|
|
@@ -191,7 +199,8 @@ export async function POST(req: Request) {
|
|
|
191
199
|
}
|
|
192
200
|
|
|
193
201
|
// Generate conversation ID if not provided (new chat)
|
|
194
|
-
const conversationId =
|
|
202
|
+
const conversationId =
|
|
203
|
+
providedConversationId ||
|
|
195
204
|
`conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
|
|
196
205
|
const isNewConversation = !providedConversationId;
|
|
197
206
|
|
|
@@ -202,20 +211,28 @@ export async function POST(req: Request) {
|
|
|
202
211
|
// Convert UIMessage[] from useChat to ModelMessage[] for streamText
|
|
203
212
|
// Note: In AI SDK v6+, convertToModelMessages may return a Promise
|
|
204
213
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
205
|
-
const modelMessagesResult = convertToModelMessages(
|
|
214
|
+
const modelMessagesResult = convertToModelMessages(
|
|
215
|
+
normalizedMessages as any,
|
|
216
|
+
);
|
|
206
217
|
const modelMessages =
|
|
207
218
|
modelMessagesResult instanceof Promise
|
|
208
219
|
? await modelMessagesResult
|
|
209
220
|
: modelMessagesResult;
|
|
210
221
|
|
|
211
222
|
// Get the first user message for title generation
|
|
212
|
-
const firstUserMessage = messages.find(
|
|
213
|
-
role: string
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
223
|
+
const firstUserMessage = messages.find(
|
|
224
|
+
(m: { role: string }) => m.role === "user",
|
|
225
|
+
) as
|
|
226
|
+
| {
|
|
227
|
+
role: string;
|
|
228
|
+
content?: string;
|
|
229
|
+
parts?: Array<{ type: string; text?: string }>;
|
|
230
|
+
}
|
|
231
|
+
| undefined;
|
|
232
|
+
|
|
233
|
+
const messageText = firstUserMessage
|
|
234
|
+
? getMessageText(firstUserMessage)
|
|
235
|
+
: "";
|
|
219
236
|
|
|
220
237
|
// Use createUIMessageStream to send both LLM text and layer events
|
|
221
238
|
return createUIMessageStreamResponse({
|
|
@@ -52,7 +52,7 @@ function getCortexMemoryConfig(
|
|
|
52
52
|
memorySpaceId: string,
|
|
53
53
|
userId: string,
|
|
54
54
|
conversationId: string,
|
|
55
|
-
layerObserver?: LayerObserver
|
|
55
|
+
layerObserver?: LayerObserver,
|
|
56
56
|
): CortexMemoryConfig {
|
|
57
57
|
return {
|
|
58
58
|
convexUrl: process.env.CONVEX_URL!,
|
|
@@ -191,7 +191,7 @@ export async function POST(req: Request) {
|
|
|
191
191
|
if (!messages || !Array.isArray(messages)) {
|
|
192
192
|
return new Response(
|
|
193
193
|
JSON.stringify({ error: "messages array is required" }),
|
|
194
|
-
{ status: 400, headers: { "Content-Type": "application/json" } }
|
|
194
|
+
{ status: 400, headers: { "Content-Type": "application/json" } },
|
|
195
195
|
);
|
|
196
196
|
}
|
|
197
197
|
|
|
@@ -206,7 +206,9 @@ export async function POST(req: Request) {
|
|
|
206
206
|
|
|
207
207
|
// Convert to model messages
|
|
208
208
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
209
|
-
const modelMessagesResult = convertToModelMessages(
|
|
209
|
+
const modelMessagesResult = convertToModelMessages(
|
|
210
|
+
normalizedMessages as any,
|
|
211
|
+
);
|
|
210
212
|
const modelMessages =
|
|
211
213
|
modelMessagesResult instanceof Promise
|
|
212
214
|
? await modelMessagesResult
|
|
@@ -214,14 +216,18 @@ export async function POST(req: Request) {
|
|
|
214
216
|
|
|
215
217
|
// Get first user message for title
|
|
216
218
|
const firstUserMessage = messages.find(
|
|
217
|
-
(m: { role: string }) => m.role === "user"
|
|
218
|
-
) as
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
219
|
+
(m: { role: string }) => m.role === "user",
|
|
220
|
+
) as
|
|
221
|
+
| {
|
|
222
|
+
role: string;
|
|
223
|
+
content?: string;
|
|
224
|
+
parts?: Array<{ type: string; text?: string }>;
|
|
225
|
+
}
|
|
226
|
+
| undefined;
|
|
227
|
+
|
|
228
|
+
const messageText = firstUserMessage
|
|
229
|
+
? getMessageText(firstUserMessage)
|
|
230
|
+
: "";
|
|
225
231
|
|
|
226
232
|
// Use createUIMessageStreamResponse - same as v5 for full memory support
|
|
227
233
|
return createUIMessageStreamResponse({
|
|
@@ -270,7 +276,7 @@ export async function POST(req: Request) {
|
|
|
270
276
|
memorySpaceId,
|
|
271
277
|
userId,
|
|
272
278
|
conversationId,
|
|
273
|
-
layerObserver
|
|
279
|
+
layerObserver,
|
|
274
280
|
);
|
|
275
281
|
|
|
276
282
|
// Create memory-augmented model - THIS handles both recall AND storage!
|
|
@@ -327,7 +333,7 @@ export async function POST(req: Request) {
|
|
|
327
333
|
{
|
|
328
334
|
status: 500,
|
|
329
335
|
headers: { "Content-Type": "application/json" },
|
|
330
|
-
}
|
|
336
|
+
},
|
|
331
337
|
);
|
|
332
338
|
}
|
|
333
339
|
}
|