forked from bai/curriculum-project-hub
feat(hub): add org sessions and usage admin APIs
List project agent sessions/runs and aggregate AgentRun cost and token usage by project and optional folder for org admins.
This commit is contained in:
@@ -0,0 +1,114 @@
|
||||
/**
|
||||
* Agent session / run listing for org admin.
|
||||
*/
|
||||
import type { PrismaClient } from "@prisma/client";
|
||||
|
||||
export async function listProjectSessions(
|
||||
prisma: PrismaClient,
|
||||
input: {
|
||||
readonly organizationId: string;
|
||||
readonly projectId: string;
|
||||
readonly limit?: number | undefined;
|
||||
},
|
||||
) {
|
||||
const project = await prisma.project.findFirst({
|
||||
where: { id: input.projectId, organizationId: input.organizationId },
|
||||
select: { id: true },
|
||||
});
|
||||
if (project === null) {
|
||||
throw new Error(`project not found: ${input.projectId}`);
|
||||
}
|
||||
const limit = Math.min(Math.max(input.limit ?? 50, 1), 200);
|
||||
const sessions = await prisma.agentSession.findMany({
|
||||
where: { projectId: project.id, archivedAt: null },
|
||||
select: {
|
||||
id: true,
|
||||
provider: true,
|
||||
roleId: true,
|
||||
model: true,
|
||||
title: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
_count: { select: { runs: true } },
|
||||
},
|
||||
orderBy: { updatedAt: "desc" },
|
||||
take: limit,
|
||||
});
|
||||
return sessions.map((s) => ({
|
||||
id: s.id,
|
||||
provider: s.provider,
|
||||
roleId: s.roleId,
|
||||
model: s.model,
|
||||
title: s.title,
|
||||
runCount: s._count.runs,
|
||||
createdAt: s.createdAt.toISOString(),
|
||||
updatedAt: s.updatedAt.toISOString(),
|
||||
}));
|
||||
}
|
||||
|
||||
export async function getSessionDetail(
|
||||
prisma: PrismaClient,
|
||||
input: { readonly organizationId: string; readonly sessionId: string },
|
||||
) {
|
||||
const session = await prisma.agentSession.findUnique({
|
||||
where: { id: input.sessionId },
|
||||
select: {
|
||||
id: true,
|
||||
provider: true,
|
||||
roleId: true,
|
||||
model: true,
|
||||
title: true,
|
||||
createdAt: true,
|
||||
updatedAt: true,
|
||||
archivedAt: true,
|
||||
project: {
|
||||
select: { id: true, name: true, organizationId: true },
|
||||
},
|
||||
runs: {
|
||||
select: {
|
||||
id: true,
|
||||
status: true,
|
||||
model: true,
|
||||
provider: true,
|
||||
inputTokens: true,
|
||||
outputTokens: true,
|
||||
costUsd: true,
|
||||
startedAt: true,
|
||||
finishedAt: true,
|
||||
error: true,
|
||||
},
|
||||
orderBy: { startedAt: "desc" },
|
||||
take: 100,
|
||||
},
|
||||
},
|
||||
});
|
||||
if (session === null || session.project.organizationId !== input.organizationId) {
|
||||
throw new Error(`session not found: ${input.sessionId}`);
|
||||
}
|
||||
return {
|
||||
id: session.id,
|
||||
provider: session.provider,
|
||||
roleId: session.roleId,
|
||||
model: session.model,
|
||||
title: session.title,
|
||||
createdAt: session.createdAt.toISOString(),
|
||||
updatedAt: session.updatedAt.toISOString(),
|
||||
archivedAt: session.archivedAt?.toISOString() ?? null,
|
||||
project: {
|
||||
id: session.project.id,
|
||||
name: session.project.name,
|
||||
},
|
||||
runs: session.runs.map((r) => ({
|
||||
id: r.id,
|
||||
status: r.status,
|
||||
model: r.model,
|
||||
provider: r.provider,
|
||||
inputTokens: r.inputTokens,
|
||||
outputTokens: r.outputTokens,
|
||||
costUsd: r.costUsd === null ? null : Number(r.costUsd),
|
||||
startedAt: r.startedAt.toISOString(),
|
||||
finishedAt: r.finishedAt?.toISOString() ?? null,
|
||||
error: r.error,
|
||||
})),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user