Skip to content

Commit 4246c08

Browse files
authored
Merge pull request #62 from codervisor:copilot/implement-missing-api-endpoints
Implement missing API endpoints and fix AgentEvent→AgentSession schema relation
2 parents 67fa9f3 + 6d78026 commit 4246c08

File tree

13 files changed

+756
-140
lines changed

13 files changed

+756
-140
lines changed
Lines changed: 7 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/**
22
* Chat Session Events API Endpoint
3-
*
3+
*
44
* GET /api/chat-sessions/[sessionId]/events - Get session events
55
*/
66

@@ -12,24 +12,18 @@ export const dynamic = 'force-dynamic';
1212

1313
/**
1414
* GET /api/chat-sessions/:sessionId/events - Get events for a chat session
15-
*
15+
*
1616
* Returns all agent events associated with the specified chat session,
1717
* ordered chronologically.
1818
*/
19-
export async function GET(
20-
request: NextRequest,
21-
{ params }: { params: { sessionId: string } }
22-
) {
19+
export async function GET(request: NextRequest, { params }: { params: { sessionId: string } }) {
2320
try {
2421
const { sessionId } = params;
2522

2623
// Validate UUID format
2724
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
2825
if (!uuidRegex.test(sessionId)) {
29-
return NextResponse.json(
30-
{ error: 'Invalid session ID format' },
31-
{ status: 400 }
32-
);
26+
return NextResponse.json({ error: 'Invalid session ID format' }, { status: 400 });
3327
}
3428

3529
// Get Prisma client
@@ -40,16 +34,8 @@ export async function GET(
4034
where: { sessionId },
4135
orderBy: { timestamp: 'asc' },
4236
include: {
43-
session: {
44-
include: {
45-
workspace: {
46-
include: {
47-
machine: true,
48-
project: true,
49-
},
50-
},
51-
},
52-
},
37+
session: true,
38+
project: true,
5339
},
5440
});
5541

@@ -64,7 +50,7 @@ export async function GET(
6450
{
6551
error: error instanceof Error ? error.message : 'Failed to get session events',
6652
},
67-
{ status: 500 }
53+
{ status: 500 },
6854
);
6955
}
7056
}

apps/web/app/api/events/route.ts

Lines changed: 107 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1-
import { NextRequest } from 'next/server';
2-
import { activeConnections } from '@/lib/api/server-realtime';
1+
import { NextRequest, NextResponse } from 'next/server';
2+
import { activeConnections, broadcastUpdate } from '@/lib/api/server-realtime';
3+
import { AgentEventService } from '@codervisor/devlog-core/server';
4+
import type { CreateAgentEventInput } from '@codervisor/devlog-core/types-only';
35

46
// Mark this route as dynamic to prevent static generation
57
export const dynamic = 'force-dynamic';
68

9+
/**
10+
* GET /api/events - Server-Sent Events (SSE) stream for real-time event updates
11+
*/
712
export async function GET(request: NextRequest) {
813
// Create a readable stream for SSE
914
console.log('[SSE Route] Creating ReadableStream...');
@@ -59,3 +64,103 @@ export async function GET(request: NextRequest) {
5964
},
6065
});
6166
}
67+
68+
/**
69+
* POST /api/events - Create a single agent event
70+
*
71+
* Creates a single event and broadcasts it to active SSE connections
72+
*/
73+
export async function POST(request: NextRequest) {
74+
try {
75+
const body = await request.json();
76+
77+
// Validate required fields
78+
if (
79+
!body.type ||
80+
!body.agentId ||
81+
!body.agentVersion ||
82+
!body.sessionId ||
83+
!body.projectId ||
84+
!body.context ||
85+
!body.data
86+
) {
87+
return NextResponse.json(
88+
{
89+
success: false,
90+
error:
91+
'Missing required fields: type, agentId, agentVersion, sessionId, projectId, context, data',
92+
},
93+
{ status: 400 },
94+
);
95+
}
96+
97+
// Validate context required field
98+
if (!body.context.workingDirectory) {
99+
return NextResponse.json(
100+
{
101+
success: false,
102+
error: 'Missing required context field: workingDirectory',
103+
},
104+
{ status: 400 },
105+
);
106+
}
107+
108+
// Validate and parse projectId
109+
const projectId =
110+
typeof body.projectId === 'number' ? body.projectId : parseInt(body.projectId, 10);
111+
if (isNaN(projectId) || projectId <= 0) {
112+
return NextResponse.json(
113+
{
114+
success: false,
115+
error: 'Invalid projectId: must be a positive integer',
116+
},
117+
{ status: 400 },
118+
);
119+
}
120+
121+
// Create event input
122+
const eventInput: CreateAgentEventInput = {
123+
type: body.type,
124+
agentId: body.agentId,
125+
agentVersion: body.agentVersion,
126+
sessionId: body.sessionId,
127+
projectId: projectId,
128+
context: body.context,
129+
data: body.data,
130+
metrics: body.metrics,
131+
parentEventId: body.parentEventId,
132+
relatedEventIds: body.relatedEventIds,
133+
tags: body.tags,
134+
severity: body.severity || 'info',
135+
};
136+
137+
const eventService = AgentEventService.getInstance(eventInput.projectId);
138+
await eventService.initialize();
139+
140+
// Create the event
141+
const event = await eventService.collectEvent(eventInput);
142+
143+
// Broadcast to active SSE connections
144+
broadcastUpdate('event_created', {
145+
event: event,
146+
timestamp: new Date().toISOString(),
147+
});
148+
149+
return NextResponse.json(
150+
{
151+
success: true,
152+
data: event,
153+
},
154+
{ status: 201 },
155+
);
156+
} catch (error) {
157+
console.error('Error creating event:', error);
158+
return NextResponse.json(
159+
{
160+
success: false,
161+
error: error instanceof Error ? error.message : 'Failed to create event',
162+
},
163+
{ status: 500 },
164+
);
165+
}
166+
}

apps/web/app/api/events/stream/route.ts

Lines changed: 9 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
/**
22
* Server-Sent Events (SSE) endpoint for real-time updates
3-
*
3+
*
44
* Provides a persistent connection that streams updates about:
55
* - New agent events
66
* - Session status changes
77
* - Dashboard metrics updates
8-
*
8+
*
99
* Supports hierarchy-based filtering:
1010
* - projectId: Filter events by project
1111
* - machineId: Filter events by machine
@@ -45,7 +45,7 @@ export async function GET(request: NextRequest) {
4545
let lastTimestamp = new Date();
4646

4747
// Send initial connection message
48-
const connectionMessage = `event: connected\ndata: ${JSON.stringify({
48+
const connectionMessage = `event: connected\ndata: ${JSON.stringify({
4949
timestamp: new Date().toISOString(),
5050
filters,
5151
})}\n\n`;
@@ -67,7 +67,7 @@ export async function GET(request: NextRequest) {
6767
const pollInterval = setInterval(async () => {
6868
try {
6969
const prisma = getPrismaClient();
70-
70+
7171
// Build where clause based on filters
7272
const where: any = {
7373
timestamp: {
@@ -79,37 +79,17 @@ export async function GET(request: NextRequest) {
7979
where.projectId = filters.projectId;
8080
}
8181

82-
if (filters.machineId) {
83-
where.session = {
84-
workspace: {
85-
machineId: filters.machineId,
86-
},
87-
};
88-
}
89-
90-
if (filters.workspaceId) {
91-
where.session = {
92-
...where.session,
93-
workspaceId: filters.workspaceId,
94-
};
95-
}
82+
// Note: machineId and workspaceId filters not applicable to AgentSession
83+
// These filters are for ChatSession which has workspace relation
9684

9785
// Fetch new events
9886
const events = await prisma.agentEvent.findMany({
9987
where,
10088
orderBy: { timestamp: 'desc' },
10189
take: 50,
10290
include: {
103-
session: {
104-
include: {
105-
workspace: {
106-
include: {
107-
machine: true,
108-
project: true,
109-
},
110-
},
111-
},
112-
},
91+
session: true,
92+
project: true,
11393
},
11494
});
11595

@@ -151,7 +131,7 @@ export async function GET(request: NextRequest) {
151131
headers: {
152132
'Content-Type': 'text/event-stream',
153133
'Cache-Control': 'no-cache, no-transform',
154-
'Connection': 'keep-alive',
134+
Connection: 'keep-alive',
155135
'X-Accel-Buffering': 'no', // Disable nginx buffering
156136
},
157137
});

apps/web/app/api/projects/[name]/events/route.ts

Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -46,22 +46,8 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
4646
projectId,
4747
};
4848

49-
// Filter by machine (via workspace via session)
50-
if (machineId) {
51-
where.session = {
52-
workspace: {
53-
machineId: parseInt(machineId, 10),
54-
},
55-
};
56-
}
57-
58-
// Filter by workspace (via session)
59-
if (workspaceId) {
60-
where.session = {
61-
...where.session,
62-
workspaceId: parseInt(workspaceId, 10),
63-
};
64-
}
49+
// Note: machineId and workspaceId filters are not applicable to AgentSession
50+
// AgentSession is workspace-independent and tracks agent activity across the project
6551

6652
// Filter by timestamp range
6753
if (from || to) {
@@ -111,16 +97,8 @@ export async function GET(request: NextRequest, { params }: { params: { id: stri
11197
orderBy: { timestamp: 'desc' },
11298
take: limit,
11399
include: {
114-
session: {
115-
include: {
116-
workspace: {
117-
include: {
118-
machine: true,
119-
project: true,
120-
},
121-
},
122-
},
123-
},
100+
session: true,
101+
project: true,
124102
},
125103
});
126104

0 commit comments

Comments
 (0)