@cortexmemory/cli 0.26.2 → 0.27.3

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.
Files changed (90) hide show
  1. package/dist/commands/convex.js +1 -1
  2. package/dist/commands/convex.js.map +1 -1
  3. package/dist/commands/deploy.d.ts +1 -1
  4. package/dist/commands/deploy.d.ts.map +1 -1
  5. package/dist/commands/deploy.js +771 -144
  6. package/dist/commands/deploy.js.map +1 -1
  7. package/dist/commands/dev.d.ts.map +1 -1
  8. package/dist/commands/dev.js +210 -36
  9. package/dist/commands/dev.js.map +1 -1
  10. package/dist/commands/init.d.ts.map +1 -1
  11. package/dist/commands/init.js +273 -43
  12. package/dist/commands/init.js.map +1 -1
  13. package/dist/commands/setup.d.ts.map +1 -1
  14. package/dist/commands/setup.js +102 -46
  15. package/dist/commands/setup.js.map +1 -1
  16. package/dist/commands/status.d.ts.map +1 -1
  17. package/dist/commands/status.js +94 -7
  18. package/dist/commands/status.js.map +1 -1
  19. package/dist/index.js +1 -1
  20. package/dist/types.d.ts +23 -0
  21. package/dist/types.d.ts.map +1 -1
  22. package/dist/utils/app-template-sync.d.ts +95 -0
  23. package/dist/utils/app-template-sync.d.ts.map +1 -0
  24. package/dist/utils/app-template-sync.js +425 -0
  25. package/dist/utils/app-template-sync.js.map +1 -0
  26. package/dist/utils/config.d.ts +11 -0
  27. package/dist/utils/config.d.ts.map +1 -1
  28. package/dist/utils/config.js +20 -0
  29. package/dist/utils/config.js.map +1 -1
  30. package/dist/utils/deployment-selector.d.ts +21 -0
  31. package/dist/utils/deployment-selector.d.ts.map +1 -1
  32. package/dist/utils/deployment-selector.js +32 -0
  33. package/dist/utils/deployment-selector.js.map +1 -1
  34. package/dist/utils/init/graph-setup.d.ts.map +1 -1
  35. package/dist/utils/init/graph-setup.js +25 -2
  36. package/dist/utils/init/graph-setup.js.map +1 -1
  37. package/dist/utils/init/quickstart-setup.d.ts +87 -0
  38. package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
  39. package/dist/utils/init/quickstart-setup.js +462 -0
  40. package/dist/utils/init/quickstart-setup.js.map +1 -0
  41. package/dist/utils/schema-sync.d.ts.map +1 -1
  42. package/dist/utils/schema-sync.js +27 -21
  43. package/dist/utils/schema-sync.js.map +1 -1
  44. package/package.json +3 -2
  45. package/templates/vercel-ai-quickstart/.env.local.example +45 -0
  46. package/templates/vercel-ai-quickstart/README.md +280 -0
  47. package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +30 -0
  48. package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +83 -0
  49. package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +94 -0
  50. package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +59 -0
  51. package/templates/vercel-ai-quickstart/app/api/chat/route.ts +277 -0
  52. package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +179 -0
  53. package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
  54. package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
  55. package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
  56. package/templates/vercel-ai-quickstart/app/globals.css +275 -0
  57. package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
  58. package/templates/vercel-ai-quickstart/app/page.tsx +216 -0
  59. package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +139 -0
  60. package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +283 -0
  61. package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +323 -0
  62. package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +334 -0
  63. package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
  64. package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
  65. package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
  66. package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
  67. package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
  68. package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +202 -0
  69. package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
  70. package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
  71. package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
  72. package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
  73. package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
  74. package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
  75. package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
  76. package/templates/vercel-ai-quickstart/jest.config.js +45 -0
  77. package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
  78. package/templates/vercel-ai-quickstart/lib/cortex.ts +27 -0
  79. package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
  80. package/templates/vercel-ai-quickstart/lib/password.ts +120 -0
  81. package/templates/vercel-ai-quickstart/next.config.js +27 -0
  82. package/templates/vercel-ai-quickstart/package.json +46 -0
  83. package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
  84. package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
  85. package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +263 -0
  86. package/templates/vercel-ai-quickstart/tests/helpers/setup.ts +48 -0
  87. package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +455 -0
  88. package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +461 -0
  89. package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +228 -0
  90. package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
@@ -0,0 +1,94 @@
1
+ /**
2
+ * User Registration API Route
3
+ *
4
+ * POST: Register a new user account
5
+ */
6
+
7
+ import { getCortex } from "@/lib/cortex";
8
+ import { hashPassword, generateSessionToken } from "@/lib/password";
9
+
10
+ export async function POST(req: Request) {
11
+ try {
12
+ const body = await req.json();
13
+ const { username, password, displayName } = body;
14
+
15
+ // Validate input
16
+ if (!username || typeof username !== "string") {
17
+ return Response.json(
18
+ { error: "Username is required" },
19
+ { status: 400 }
20
+ );
21
+ }
22
+
23
+ if (!password || typeof password !== "string") {
24
+ return Response.json(
25
+ { error: "Password is required" },
26
+ { status: 400 }
27
+ );
28
+ }
29
+
30
+ if (username.length < 2) {
31
+ return Response.json(
32
+ { error: "Username must be at least 2 characters" },
33
+ { status: 400 }
34
+ );
35
+ }
36
+
37
+ if (password.length < 4) {
38
+ return Response.json(
39
+ { error: "Password must be at least 4 characters" },
40
+ { status: 400 }
41
+ );
42
+ }
43
+
44
+ // Sanitize username (alphanumeric, underscore, hyphen only)
45
+ const sanitizedUsername = username.toLowerCase().replace(/[^a-z0-9_-]/g, "");
46
+ if (sanitizedUsername !== username.toLowerCase()) {
47
+ return Response.json(
48
+ { error: "Username can only contain letters, numbers, underscores, and hyphens" },
49
+ { status: 400 }
50
+ );
51
+ }
52
+
53
+ const cortex = getCortex();
54
+
55
+ // Check if user already exists
56
+ const existingUser = await cortex.users.get(sanitizedUsername);
57
+ if (existingUser) {
58
+ return Response.json(
59
+ { error: "Username already taken" },
60
+ { status: 409 }
61
+ );
62
+ }
63
+
64
+ // Hash password and create user profile
65
+ const passwordHash = await hashPassword(password);
66
+ const now = Date.now();
67
+
68
+ await cortex.users.update(sanitizedUsername, {
69
+ displayName: displayName || sanitizedUsername,
70
+ passwordHash,
71
+ createdAt: now,
72
+ lastLoginAt: now,
73
+ });
74
+
75
+ // Generate session token
76
+ const sessionToken = generateSessionToken();
77
+
78
+ return Response.json({
79
+ success: true,
80
+ user: {
81
+ id: sanitizedUsername,
82
+ displayName: displayName || sanitizedUsername,
83
+ },
84
+ sessionToken,
85
+ });
86
+ } catch (error) {
87
+ console.error("[Register Error]", error);
88
+
89
+ return Response.json(
90
+ { error: "Failed to register user" },
91
+ { status: 500 }
92
+ );
93
+ }
94
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Admin Setup API Route
3
+ *
4
+ * POST: Set up initial admin password (first-run only)
5
+ */
6
+
7
+ import { getCortex } from "@/lib/cortex";
8
+ import { hashPassword } from "@/lib/password";
9
+
10
+ const ADMIN_NAMESPACE = "quickstart-config";
11
+ const ADMIN_KEY = "admin_password_hash";
12
+
13
+ export async function POST(req: Request) {
14
+ try {
15
+ const body = await req.json();
16
+ const { password } = body;
17
+
18
+ if (!password || typeof password !== "string") {
19
+ return Response.json(
20
+ { error: "Password is required" },
21
+ { status: 400 }
22
+ );
23
+ }
24
+
25
+ if (password.length < 4) {
26
+ return Response.json(
27
+ { error: "Password must be at least 4 characters" },
28
+ { status: 400 }
29
+ );
30
+ }
31
+
32
+ const cortex = getCortex();
33
+
34
+ // Check if admin already exists
35
+ const existingHash = await cortex.mutable.get(ADMIN_NAMESPACE, ADMIN_KEY);
36
+ if (existingHash !== null) {
37
+ return Response.json(
38
+ { error: "Admin already configured" },
39
+ { status: 409 }
40
+ );
41
+ }
42
+
43
+ // Hash and store admin password
44
+ const passwordHash = await hashPassword(password);
45
+ await cortex.mutable.set(ADMIN_NAMESPACE, ADMIN_KEY, passwordHash);
46
+
47
+ return Response.json({
48
+ success: true,
49
+ message: "Admin password configured successfully",
50
+ });
51
+ } catch (error) {
52
+ console.error("[Admin Setup Error]", error);
53
+
54
+ return Response.json(
55
+ { error: "Failed to configure admin password" },
56
+ { status: 500 }
57
+ );
58
+ }
59
+ }
@@ -0,0 +1,277 @@
1
+ import { createCortexMemoryAsync } from "@cortexmemory/vercel-ai-provider";
2
+ import type {
3
+ LayerObserver,
4
+ CortexMemoryConfig,
5
+ } from "@cortexmemory/vercel-ai-provider";
6
+ import { openai, createOpenAI } from "@ai-sdk/openai";
7
+ import {
8
+ streamText,
9
+ embed,
10
+ convertToModelMessages,
11
+ createUIMessageStream,
12
+ createUIMessageStreamResponse,
13
+ } from "ai";
14
+ import { getCortex } from "@/lib/cortex";
15
+
16
+ // Create OpenAI client for embeddings
17
+ const openaiClient = createOpenAI({ apiKey: process.env.OPENAI_API_KEY });
18
+
19
+ // System prompt for the assistant
20
+ const SYSTEM_PROMPT = `You are a helpful AI assistant with long-term memory powered by Cortex.
21
+
22
+ Your capabilities:
23
+ - You remember everything users tell you across conversations
24
+ - You can recall facts, preferences, and context from past interactions
25
+ - You naturally reference what you've learned about the user
26
+
27
+ Behavior guidelines:
28
+ - When you remember something from a previous conversation, mention it naturally
29
+ - If asked about something you learned, reference it specifically
30
+ - Be conversational and friendly
31
+ - Help demonstrate the memory system by showing what you remember
32
+
33
+ Example interactions:
34
+ - User: "My name is Alex" → Remember and use their name
35
+ - User: "I work at Acme Corp" → Remember their employer
36
+ - User: "My favorite color is blue" → Remember their preference
37
+ - User: "What do you know about me?" → List everything you remember`;
38
+
39
+ // Create Cortex Memory config factory
40
+ // Uses createCortexMemoryAsync for graph support when CORTEX_GRAPH_SYNC=true
41
+ function getCortexMemoryConfig(
42
+ memorySpaceId: string,
43
+ userId: string,
44
+ conversationId: string,
45
+ layerObserver?: LayerObserver,
46
+ ): CortexMemoryConfig {
47
+ return {
48
+ convexUrl: process.env.CONVEX_URL!,
49
+ memorySpaceId,
50
+
51
+ // User identification
52
+ userId,
53
+ userName: "Demo User",
54
+
55
+ // Agent identification (required for user-agent conversations in SDK v0.17.0+)
56
+ agentId: "quickstart-assistant",
57
+ agentName: "Cortex Demo Assistant",
58
+
59
+ // Conversation ID for chat history isolation
60
+ conversationId,
61
+
62
+ // Enable graph memory sync (auto-configured via env vars)
63
+ // When true, uses CypherGraphAdapter to sync to Neo4j/Memgraph
64
+ enableGraphMemory: process.env.CORTEX_GRAPH_SYNC === "true",
65
+
66
+ // Enable fact extraction (auto-configured via env vars)
67
+ enableFactExtraction: process.env.CORTEX_FACT_EXTRACTION === "true",
68
+
69
+ // Belief Revision (v0.24.0+)
70
+ // Automatically handles fact updates, supersessions, and deduplication
71
+ // When a user changes their preference (e.g., "I now prefer purple"),
72
+ // the system intelligently updates or supersedes the old fact.
73
+ beliefRevision: {
74
+ enabled: true, // Enable the belief revision pipeline
75
+ slotMatching: true, // Fast slot-based conflict detection (subject-predicate matching)
76
+ llmResolution: true, // LLM-based resolution for nuanced conflicts
77
+ },
78
+
79
+ // Embedding provider for semantic matching (required for semantic dedup & belief revision)
80
+ embeddingProvider: {
81
+ generate: async (text: string) => {
82
+ const result = await embed({
83
+ model: openaiClient.embedding("text-embedding-3-small"),
84
+ value: text,
85
+ });
86
+ return result.embedding;
87
+ },
88
+ },
89
+
90
+ // Streaming enhancements
91
+ streamingOptions: {
92
+ storePartialResponse: true,
93
+ progressiveFactExtraction: true,
94
+ enableAdaptiveProcessing: true,
95
+ },
96
+
97
+ // Memory recall configuration (v0.23.0 - unified retrieval across all layers)
98
+ memorySearchLimit: 20, // Results from combined vector + facts + graph search
99
+
100
+ // Real-time layer tracking (v0.24.0+)
101
+ // Events are emitted as each layer processes, enabling live UI updates
102
+ layerObserver,
103
+
104
+ // Debug in development
105
+ debug: process.env.NODE_ENV === "development",
106
+ };
107
+ }
108
+
109
+ /**
110
+ * Generate a title from the first user message
111
+ */
112
+ function generateTitle(message: string): string {
113
+ // Take first 50 chars, cut at word boundary
114
+ let title = message.slice(0, 50);
115
+ if (message.length > 50) {
116
+ const lastSpace = title.lastIndexOf(" ");
117
+ if (lastSpace > 20) {
118
+ title = title.slice(0, lastSpace);
119
+ }
120
+ title += "...";
121
+ }
122
+ return title;
123
+ }
124
+
125
+ export async function POST(req: Request) {
126
+ try {
127
+ const body = await req.json();
128
+ const { messages, memorySpaceId, userId, conversationId: providedConversationId } = body;
129
+
130
+ // Generate conversation ID if not provided (new chat)
131
+ const conversationId = providedConversationId ||
132
+ `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
133
+ const isNewConversation = !providedConversationId;
134
+
135
+ // Convert UIMessage[] from useChat to ModelMessage[] for streamText
136
+ // Note: In AI SDK v6+, convertToModelMessages may return a Promise
137
+ const modelMessagesResult = convertToModelMessages(messages);
138
+ const modelMessages =
139
+ modelMessagesResult instanceof Promise
140
+ ? await modelMessagesResult
141
+ : modelMessagesResult;
142
+
143
+ // Get the first user message for title generation
144
+ // AI SDK v5+ uses `parts` array instead of `content` string
145
+ const firstUserMessage = messages.find((m: { role: string }) => m.role === "user") as {
146
+ role: string;
147
+ content?: string;
148
+ parts?: Array<{ type: string; text?: string }>;
149
+ } | undefined;
150
+
151
+ let messageText = "";
152
+ if (firstUserMessage) {
153
+ if (typeof firstUserMessage.content === "string") {
154
+ // Legacy format: content is a string
155
+ messageText = firstUserMessage.content;
156
+ } else if (firstUserMessage.parts && Array.isArray(firstUserMessage.parts)) {
157
+ // AI SDK v5+ format: extract text from parts array
158
+ messageText = firstUserMessage.parts
159
+ .filter((part) => part.type === "text" && part.text)
160
+ .map((part) => part.text)
161
+ .join("");
162
+ }
163
+ }
164
+
165
+ // Use createUIMessageStream to send both LLM text and layer events
166
+ return createUIMessageStreamResponse({
167
+ stream: createUIMessageStream({
168
+ execute: async ({ writer }) => {
169
+ // Create observer that writes layer events to the stream
170
+ // These events are transient (not persisted in message history)
171
+ const layerObserver: LayerObserver = {
172
+ onOrchestrationStart: (orchestrationId) => {
173
+ writer.write({
174
+ type: "data-orchestration-start",
175
+ data: { orchestrationId },
176
+ transient: true,
177
+ });
178
+ },
179
+ onLayerUpdate: (event) => {
180
+ writer.write({
181
+ type: "data-layer-update",
182
+ data: {
183
+ layer: event.layer,
184
+ status: event.status,
185
+ timestamp: event.timestamp,
186
+ latencyMs: event.latencyMs,
187
+ data: event.data,
188
+ error: event.error,
189
+ revisionAction: event.revisionAction,
190
+ supersededFacts: event.supersededFacts,
191
+ },
192
+ transient: true,
193
+ });
194
+ },
195
+ onOrchestrationComplete: (summary) => {
196
+ writer.write({
197
+ type: "data-orchestration-complete",
198
+ data: {
199
+ orchestrationId: summary.orchestrationId,
200
+ totalLatencyMs: summary.totalLatencyMs,
201
+ createdIds: summary.createdIds,
202
+ },
203
+ transient: true,
204
+ });
205
+ },
206
+ };
207
+
208
+ // Build config with the observer and conversation ID
209
+ const config = getCortexMemoryConfig(
210
+ memorySpaceId || "quickstart-demo",
211
+ userId || "demo-user",
212
+ conversationId,
213
+ layerObserver,
214
+ );
215
+
216
+ // Create memory-augmented model with async initialization (enables graph support)
217
+ // This connects to Neo4j/Memgraph if CORTEX_GRAPH_SYNC=true
218
+ const cortexMemory = await createCortexMemoryAsync(config);
219
+
220
+ // Stream response with automatic memory integration
221
+ const result = streamText({
222
+ model: cortexMemory(openai("gpt-4o-mini")),
223
+ messages: modelMessages,
224
+ system: SYSTEM_PROMPT,
225
+ });
226
+
227
+ // Merge LLM stream into the UI message stream
228
+ writer.merge(result.toUIMessageStream());
229
+
230
+ // If this is a new conversation, create it in the SDK and update the title
231
+ if (isNewConversation && messageText) {
232
+ try {
233
+ const cortex = getCortex();
234
+ const title = generateTitle(messageText);
235
+
236
+ // Create the conversation with the SDK
237
+ await cortex.conversations.create({
238
+ memorySpaceId: memorySpaceId || "quickstart-demo",
239
+ conversationId,
240
+ type: "user-agent",
241
+ participants: {
242
+ userId: userId || "demo-user",
243
+ agentId: "quickstart-assistant",
244
+ },
245
+ metadata: { title },
246
+ });
247
+
248
+ // Send conversation update to the client
249
+ writer.write({
250
+ type: "data-conversation-update",
251
+ data: {
252
+ conversationId,
253
+ title,
254
+ },
255
+ transient: true,
256
+ });
257
+ } catch (error) {
258
+ console.error("Failed to create conversation:", error);
259
+ }
260
+ }
261
+ },
262
+ }),
263
+ });
264
+ } catch (error) {
265
+ console.error("[Chat API Error]", error);
266
+
267
+ return new Response(
268
+ JSON.stringify({
269
+ error: error instanceof Error ? error.message : "Unknown error",
270
+ }),
271
+ {
272
+ status: 500,
273
+ headers: { "Content-Type": "application/json" },
274
+ },
275
+ );
276
+ }
277
+ }
@@ -0,0 +1,179 @@
1
+ /**
2
+ * Conversations API Route
3
+ *
4
+ * GET: List conversations for a user (chat history)
5
+ * POST: Create a new conversation
6
+ * DELETE: Delete a conversation
7
+ */
8
+
9
+ import { getCortex } from "@/lib/cortex";
10
+
11
+ export async function GET(req: Request) {
12
+ try {
13
+ const { searchParams } = new URL(req.url);
14
+ const conversationId = searchParams.get("conversationId");
15
+ const userId = searchParams.get("userId");
16
+ const memorySpaceId = searchParams.get("memorySpaceId") || "quickstart-demo";
17
+
18
+ const cortex = getCortex();
19
+
20
+ // If conversationId is provided, fetch single conversation with messages
21
+ if (conversationId) {
22
+ const conversation = await cortex.conversations.get(conversationId, {
23
+ includeMessages: true,
24
+ messageLimit: 100,
25
+ });
26
+
27
+ if (!conversation) {
28
+ return Response.json(
29
+ { error: "Conversation not found" },
30
+ { status: 404 }
31
+ );
32
+ }
33
+
34
+ // Transform messages to the format expected by AI SDK useChat
35
+ const messages = (conversation.messages || []).map((msg) => ({
36
+ id: msg.id,
37
+ role: msg.role as "user" | "assistant",
38
+ content: msg.content,
39
+ createdAt: new Date(msg.timestamp),
40
+ }));
41
+
42
+ return Response.json({
43
+ conversation: {
44
+ id: conversation.conversationId,
45
+ title: (conversation.metadata?.title as string) || getDefaultTitle(conversation),
46
+ createdAt: conversation.createdAt,
47
+ updatedAt: conversation.updatedAt,
48
+ messageCount: conversation.messageCount || 0,
49
+ },
50
+ messages,
51
+ });
52
+ }
53
+
54
+ // List conversations for user (requires userId)
55
+ if (!userId) {
56
+ return Response.json(
57
+ { error: "userId is required" },
58
+ { status: 400 }
59
+ );
60
+ }
61
+
62
+ // Get conversations for the user
63
+ const result = await cortex.conversations.list({
64
+ memorySpaceId,
65
+ userId,
66
+ limit: 50,
67
+ });
68
+
69
+ // Map conversations to a simpler format for the UI
70
+ const conversations = result.conversations.map((conv) => ({
71
+ id: conv.conversationId,
72
+ title: (conv.metadata?.title as string) || getDefaultTitle(conv),
73
+ createdAt: conv.createdAt,
74
+ updatedAt: conv.updatedAt,
75
+ messageCount: conv.messageCount || 0,
76
+ }));
77
+
78
+ // Sort by updatedAt descending (most recent first)
79
+ conversations.sort((a, b) => b.updatedAt - a.updatedAt);
80
+
81
+ return Response.json({ conversations });
82
+ } catch (error) {
83
+ console.error("[Conversations Error]", error);
84
+
85
+ return Response.json(
86
+ { error: "Failed to fetch conversations" },
87
+ { status: 500 }
88
+ );
89
+ }
90
+ }
91
+
92
+ export async function POST(req: Request) {
93
+ try {
94
+ const body = await req.json();
95
+ const { userId, memorySpaceId = "quickstart-demo", title } = body;
96
+
97
+ if (!userId) {
98
+ return Response.json(
99
+ { error: "userId is required" },
100
+ { status: 400 }
101
+ );
102
+ }
103
+
104
+ const cortex = getCortex();
105
+
106
+ // Create a new conversation
107
+ const conversationId = `conv-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
108
+
109
+ const conversation = await cortex.conversations.create({
110
+ memorySpaceId,
111
+ conversationId,
112
+ type: "user-agent",
113
+ participants: {
114
+ userId,
115
+ agentId: "quickstart-assistant",
116
+ },
117
+ metadata: {
118
+ title: title || "New Chat",
119
+ },
120
+ });
121
+
122
+ return Response.json({
123
+ success: true,
124
+ conversation: {
125
+ id: conversation.conversationId,
126
+ title: (conversation.metadata?.title as string) || "New Chat",
127
+ createdAt: conversation.createdAt,
128
+ updatedAt: conversation.updatedAt,
129
+ messageCount: 0,
130
+ },
131
+ });
132
+ } catch (error) {
133
+ console.error("[Conversation Create Error]", error);
134
+
135
+ return Response.json(
136
+ { error: "Failed to create conversation" },
137
+ { status: 500 }
138
+ );
139
+ }
140
+ }
141
+
142
+ export async function DELETE(req: Request) {
143
+ try {
144
+ const { searchParams } = new URL(req.url);
145
+ const conversationId = searchParams.get("conversationId");
146
+
147
+ if (!conversationId) {
148
+ return Response.json(
149
+ { error: "conversationId is required" },
150
+ { status: 400 }
151
+ );
152
+ }
153
+
154
+ const cortex = getCortex();
155
+
156
+ await cortex.conversations.delete(conversationId);
157
+
158
+ return Response.json({ success: true });
159
+ } catch (error) {
160
+ console.error("[Conversation Delete Error]", error);
161
+
162
+ return Response.json(
163
+ { error: "Failed to delete conversation" },
164
+ { status: 500 }
165
+ );
166
+ }
167
+ }
168
+
169
+ /**
170
+ * Generate a default title from conversation data
171
+ */
172
+ function getDefaultTitle(conv: { createdAt: number; messageCount?: number }): string {
173
+ const date = new Date(conv.createdAt);
174
+ const timeStr = date.toLocaleTimeString("en-US", {
175
+ hour: "numeric",
176
+ minute: "2-digit",
177
+ });
178
+ return `Chat at ${timeStr}`;
179
+ }
@@ -0,0 +1,39 @@
1
+ import { Cortex } from "@cortexmemory/sdk";
2
+
3
+ export const dynamic = "force-dynamic";
4
+
5
+ function getCortex() {
6
+ return new Cortex({ convexUrl: process.env.CONVEX_URL! });
7
+ }
8
+
9
+ export async function GET(req: Request) {
10
+ try {
11
+ const { searchParams } = new URL(req.url);
12
+ const memorySpaceId =
13
+ searchParams.get("memorySpaceId") || "quickstart-demo";
14
+ const userId = searchParams.get("userId");
15
+ const limit = parseInt(searchParams.get("limit") || "50");
16
+
17
+ const cortex = getCortex();
18
+
19
+ // Fetch facts for the user/memory space
20
+ const facts = await cortex.facts.list({
21
+ memorySpaceId,
22
+ ...(userId ? { userId } : {}),
23
+ limit,
24
+ });
25
+
26
+ return Response.json({
27
+ facts,
28
+ count: facts.length,
29
+ memorySpaceId,
30
+ });
31
+ } catch (error) {
32
+ console.error("[Facts API Error]", error);
33
+
34
+ return Response.json(
35
+ { error: error instanceof Error ? error.message : "Unknown error" },
36
+ { status: 500 },
37
+ );
38
+ }
39
+ }