Skip to content

Commit 0af7fb2

Browse files
committed
feat(memory): added memory block and tool (#372)
* feat(memory): added memory block and service * feat(memory): ran migrations * improvement(memory): appending memories; console messages * feat(memory): added agent raw message history input UI * feat(agent-messages): added agent message history * improvement: added tests
1 parent b29827c commit 0af7fb2

25 files changed

Lines changed: 4029 additions & 94 deletions

File tree

Lines changed: 329 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,329 @@
1+
import { NextRequest, NextResponse } from 'next/server'
2+
import { and, eq, isNull } from 'drizzle-orm'
3+
import { createLogger } from '@/lib/logs/console-logger'
4+
import { db } from '@/db'
5+
import { memory } from '@/db/schema'
6+
7+
const logger = createLogger('MemoryByIdAPI')
8+
9+
export const dynamic = 'force-dynamic'
10+
export const runtime = 'nodejs'
11+
12+
/**
13+
* GET handler for retrieving a specific memory by ID
14+
*/
15+
export async function GET(
16+
request: NextRequest,
17+
{ params }: { params: Promise<{ id: string }> }
18+
) {
19+
const requestId = crypto.randomUUID().slice(0, 8)
20+
const { id } = await params
21+
22+
try {
23+
logger.info(`[${requestId}] Processing memory get request for ID: ${id}`)
24+
25+
// Get workflowId from query parameter (required)
26+
const url = new URL(request.url)
27+
const workflowId = url.searchParams.get('workflowId')
28+
29+
if (!workflowId) {
30+
logger.warn(`[${requestId}] Missing required parameter: workflowId`)
31+
return NextResponse.json(
32+
{
33+
success: false,
34+
error: {
35+
message: 'workflowId parameter is required',
36+
},
37+
},
38+
{ status: 400 }
39+
)
40+
}
41+
42+
// Query the database for the memory
43+
const memories = await db
44+
.select()
45+
.from(memory)
46+
.where(
47+
and(
48+
eq(memory.key, id),
49+
eq(memory.workflowId, workflowId),
50+
isNull(memory.deletedAt)
51+
)
52+
)
53+
.orderBy(memory.createdAt)
54+
.limit(1)
55+
56+
if (memories.length === 0) {
57+
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`)
58+
return NextResponse.json(
59+
{
60+
success: false,
61+
error: {
62+
message: 'Memory not found',
63+
},
64+
},
65+
{ status: 404 }
66+
)
67+
}
68+
69+
logger.info(`[${requestId}] Memory retrieved successfully: ${id} for workflow: ${workflowId}`)
70+
return NextResponse.json(
71+
{
72+
success: true,
73+
data: memories[0],
74+
},
75+
{ status: 200 }
76+
)
77+
78+
} catch (error: any) {
79+
return NextResponse.json(
80+
{
81+
success: false,
82+
error: {
83+
message: error.message || 'Failed to retrieve memory',
84+
},
85+
},
86+
{ status: 500 }
87+
)
88+
}
89+
}
90+
91+
/**
92+
* DELETE handler for removing a specific memory
93+
*/
94+
export async function DELETE(
95+
request: NextRequest,
96+
{ params }: { params: Promise<{ id: string }> }
97+
) {
98+
const requestId = crypto.randomUUID().slice(0, 8)
99+
const { id } = await params
100+
101+
try {
102+
logger.info(`[${requestId}] Processing memory delete request for ID: ${id}`)
103+
104+
// Get workflowId from query parameter (required)
105+
const url = new URL(request.url)
106+
const workflowId = url.searchParams.get('workflowId')
107+
108+
if (!workflowId) {
109+
logger.warn(`[${requestId}] Missing required parameter: workflowId`)
110+
return NextResponse.json(
111+
{
112+
success: false,
113+
error: {
114+
message: 'workflowId parameter is required',
115+
},
116+
},
117+
{ status: 400 }
118+
)
119+
}
120+
121+
// Verify memory exists before attempting to delete
122+
const existingMemory = await db
123+
.select({ id: memory.id })
124+
.from(memory)
125+
.where(
126+
and(
127+
eq(memory.key, id),
128+
eq(memory.workflowId, workflowId),
129+
isNull(memory.deletedAt)
130+
)
131+
)
132+
.limit(1)
133+
134+
if (existingMemory.length === 0) {
135+
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`)
136+
return NextResponse.json(
137+
{
138+
success: false,
139+
error: {
140+
message: 'Memory not found',
141+
},
142+
},
143+
{ status: 404 }
144+
)
145+
}
146+
147+
// Soft delete by setting deletedAt timestamp
148+
await db
149+
.update(memory)
150+
.set({
151+
deletedAt: new Date(),
152+
updatedAt: new Date()
153+
})
154+
.where(
155+
and(
156+
eq(memory.key, id),
157+
eq(memory.workflowId, workflowId)
158+
)
159+
)
160+
161+
logger.info(`[${requestId}] Memory deleted successfully: ${id} for workflow: ${workflowId}`)
162+
return NextResponse.json(
163+
{
164+
success: true,
165+
data: { message: 'Memory deleted successfully' },
166+
},
167+
{ status: 200 }
168+
)
169+
170+
} catch (error: any) {
171+
return NextResponse.json(
172+
{
173+
success: false,
174+
error: {
175+
message: error.message || 'Failed to delete memory',
176+
},
177+
},
178+
{ status: 500 }
179+
)
180+
}
181+
}
182+
183+
/**
184+
* PUT handler for updating a specific memory
185+
*/
186+
export async function PUT(
187+
request: NextRequest,
188+
{ params }: { params: Promise<{ id: string }> }
189+
) {
190+
const requestId = crypto.randomUUID().slice(0, 8)
191+
const { id } = await params
192+
193+
try {
194+
logger.info(`[${requestId}] Processing memory update request for ID: ${id}`)
195+
196+
// Parse request body
197+
const body = await request.json()
198+
const { data, workflowId } = body
199+
200+
if (!data) {
201+
logger.warn(`[${requestId}] Missing required field: data`)
202+
return NextResponse.json(
203+
{
204+
success: false,
205+
error: {
206+
message: 'Memory data is required',
207+
},
208+
},
209+
{ status: 400 }
210+
)
211+
}
212+
213+
if (!workflowId) {
214+
logger.warn(`[${requestId}] Missing required field: workflowId`)
215+
return NextResponse.json(
216+
{
217+
success: false,
218+
error: {
219+
message: 'workflowId is required',
220+
},
221+
},
222+
{ status: 400 }
223+
)
224+
}
225+
226+
// Verify memory exists before attempting to update
227+
const existingMemories = await db
228+
.select()
229+
.from(memory)
230+
.where(
231+
and(
232+
eq(memory.key, id),
233+
eq(memory.workflowId, workflowId),
234+
isNull(memory.deletedAt)
235+
)
236+
)
237+
.limit(1)
238+
239+
if (existingMemories.length === 0) {
240+
logger.warn(`[${requestId}] Memory not found: ${id} for workflow: ${workflowId}`)
241+
return NextResponse.json(
242+
{
243+
success: false,
244+
error: {
245+
message: 'Memory not found',
246+
},
247+
},
248+
{ status: 404 }
249+
)
250+
}
251+
252+
const existingMemory = existingMemories[0]
253+
254+
// Validate memory data based on the existing memory type
255+
if (existingMemory.type === 'agent') {
256+
if (!data.role || !data.content) {
257+
logger.warn(`[${requestId}] Missing agent memory fields`)
258+
return NextResponse.json(
259+
{
260+
success: false,
261+
error: {
262+
message: 'Agent memory requires role and content',
263+
},
264+
},
265+
{ status: 400 }
266+
)
267+
}
268+
269+
if (!['user', 'assistant', 'system'].includes(data.role)) {
270+
logger.warn(`[${requestId}] Invalid agent role: ${data.role}`)
271+
return NextResponse.json(
272+
{
273+
success: false,
274+
error: {
275+
message: 'Agent role must be user, assistant, or system',
276+
},
277+
},
278+
{ status: 400 }
279+
)
280+
}
281+
}
282+
283+
// Update the memory with new data
284+
await db
285+
.update(memory)
286+
.set({
287+
data,
288+
updatedAt: new Date()
289+
})
290+
.where(
291+
and(
292+
eq(memory.key, id),
293+
eq(memory.workflowId, workflowId)
294+
)
295+
)
296+
297+
// Fetch the updated memory
298+
const updatedMemories = await db
299+
.select()
300+
.from(memory)
301+
.where(
302+
and(
303+
eq(memory.key, id),
304+
eq(memory.workflowId, workflowId)
305+
)
306+
)
307+
.limit(1)
308+
309+
logger.info(`[${requestId}] Memory updated successfully: ${id} for workflow: ${workflowId}`)
310+
return NextResponse.json(
311+
{
312+
success: true,
313+
data: updatedMemories[0],
314+
},
315+
{ status: 200 }
316+
)
317+
318+
} catch (error: any) {
319+
return NextResponse.json(
320+
{
321+
success: false,
322+
error: {
323+
message: error.message || 'Failed to update memory',
324+
},
325+
},
326+
{ status: 500 }
327+
)
328+
}
329+
}

0 commit comments

Comments
 (0)