Skip to content

Commit b316a90

Browse files
committed
Inline store agent data fetch
1 parent 38a0d61 commit b316a90

File tree

3 files changed

+329
-333
lines changed

3 files changed

+329
-333
lines changed

web/src/app/api/agents/route.ts

Lines changed: 1 addition & 271 deletions
Original file line numberDiff line numberDiff line change
@@ -1,282 +1,12 @@
1-
import db from '@codebuff/internal/db'
2-
import * as schema from '@codebuff/internal/db/schema'
3-
import { sql, eq, and, gte } from 'drizzle-orm'
4-
import { unstable_cache } from 'next/cache'
51
import { NextResponse } from 'next/server'
62

73
import { logger } from '@/util/logger'
4+
import { getCachedAgents } from '@/server/agents-data'
85

96
// ISR Configuration for API route
107
export const revalidate = 600 // Cache for 10 minutes
118
export const dynamic = 'force-static'
129

13-
// Cached function for expensive agent aggregations
14-
const getCachedAgents = unstable_cache(
15-
async () => {
16-
const oneWeekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000)
17-
18-
// Get all published agents with their publisher info
19-
const agents = await db
20-
.select({
21-
id: schema.agentConfig.id,
22-
version: schema.agentConfig.version,
23-
data: schema.agentConfig.data,
24-
created_at: schema.agentConfig.created_at,
25-
publisher: {
26-
id: schema.publisher.id,
27-
name: schema.publisher.name,
28-
verified: schema.publisher.verified,
29-
avatar_url: schema.publisher.avatar_url,
30-
},
31-
})
32-
.from(schema.agentConfig)
33-
.innerJoin(
34-
schema.publisher,
35-
sql`${schema.agentConfig.publisher_id} = ${schema.publisher.id}`,
36-
)
37-
.orderBy(sql`${schema.agentConfig.created_at} DESC`)
38-
39-
// Get aggregated all-time usage metrics across all versions
40-
const usageMetrics = await db
41-
.select({
42-
publisher_id: schema.agentRun.publisher_id,
43-
agent_name: schema.agentRun.agent_name,
44-
total_invocations: sql<number>`COUNT(*)`,
45-
total_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
46-
avg_cost_per_run: sql<number>`COALESCE(AVG(${schema.agentRun.total_credits}) / 100.0, 0)`,
47-
unique_users: sql<number>`COUNT(DISTINCT ${schema.agentRun.user_id})`,
48-
last_used: sql<Date>`MAX(${schema.agentRun.created_at})`,
49-
})
50-
.from(schema.agentRun)
51-
.where(
52-
and(
53-
eq(schema.agentRun.status, 'completed'),
54-
sql`${schema.agentRun.agent_id} != 'test-agent'`,
55-
sql`${schema.agentRun.publisher_id} IS NOT NULL`,
56-
sql`${schema.agentRun.agent_name} IS NOT NULL`,
57-
),
58-
)
59-
.groupBy(schema.agentRun.publisher_id, schema.agentRun.agent_name)
60-
61-
// Get aggregated weekly usage metrics across all versions
62-
const weeklyMetrics = await db
63-
.select({
64-
publisher_id: schema.agentRun.publisher_id,
65-
agent_name: schema.agentRun.agent_name,
66-
weekly_runs: sql<number>`COUNT(*)`,
67-
weekly_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
68-
})
69-
.from(schema.agentRun)
70-
.where(
71-
and(
72-
eq(schema.agentRun.status, 'completed'),
73-
gte(schema.agentRun.created_at, oneWeekAgo),
74-
sql`${schema.agentRun.agent_id} != 'test-agent'`,
75-
sql`${schema.agentRun.publisher_id} IS NOT NULL`,
76-
sql`${schema.agentRun.agent_name} IS NOT NULL`,
77-
),
78-
)
79-
.groupBy(schema.agentRun.publisher_id, schema.agentRun.agent_name)
80-
81-
// Get per-version usage metrics for all-time
82-
const perVersionMetrics = await db
83-
.select({
84-
publisher_id: schema.agentRun.publisher_id,
85-
agent_name: schema.agentRun.agent_name,
86-
agent_version: schema.agentRun.agent_version,
87-
total_invocations: sql<number>`COUNT(*)`,
88-
total_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
89-
avg_cost_per_run: sql<number>`COALESCE(AVG(${schema.agentRun.total_credits}) / 100.0, 0)`,
90-
unique_users: sql<number>`COUNT(DISTINCT ${schema.agentRun.user_id})`,
91-
last_used: sql<Date>`MAX(${schema.agentRun.created_at})`,
92-
})
93-
.from(schema.agentRun)
94-
.where(
95-
and(
96-
eq(schema.agentRun.status, 'completed'),
97-
sql`${schema.agentRun.agent_id} != 'test-agent'`,
98-
sql`${schema.agentRun.publisher_id} IS NOT NULL`,
99-
sql`${schema.agentRun.agent_name} IS NOT NULL`,
100-
sql`${schema.agentRun.agent_version} IS NOT NULL`,
101-
),
102-
)
103-
.groupBy(
104-
schema.agentRun.publisher_id,
105-
schema.agentRun.agent_name,
106-
schema.agentRun.agent_version,
107-
)
108-
109-
// Get per-version weekly usage metrics
110-
const perVersionWeeklyMetrics = await db
111-
.select({
112-
publisher_id: schema.agentRun.publisher_id,
113-
agent_name: schema.agentRun.agent_name,
114-
agent_version: schema.agentRun.agent_version,
115-
weekly_runs: sql<number>`COUNT(*)`,
116-
weekly_dollars: sql<number>`COALESCE(SUM(${schema.agentRun.total_credits}) / 100.0, 0)`,
117-
})
118-
.from(schema.agentRun)
119-
.where(
120-
and(
121-
eq(schema.agentRun.status, 'completed'),
122-
gte(schema.agentRun.created_at, oneWeekAgo),
123-
sql`${schema.agentRun.agent_id} != 'test-agent'`,
124-
sql`${schema.agentRun.publisher_id} IS NOT NULL`,
125-
sql`${schema.agentRun.agent_name} IS NOT NULL`,
126-
sql`${schema.agentRun.agent_version} IS NOT NULL`,
127-
),
128-
)
129-
.groupBy(
130-
schema.agentRun.publisher_id,
131-
schema.agentRun.agent_name,
132-
schema.agentRun.agent_version,
133-
)
134-
135-
// Create weekly metrics map by publisher/agent_name
136-
const weeklyMap = new Map()
137-
weeklyMetrics.forEach((metric) => {
138-
if (metric.publisher_id && metric.agent_name) {
139-
const key = `${metric.publisher_id}/${metric.agent_name}`
140-
weeklyMap.set(key, {
141-
weekly_runs: Number(metric.weekly_runs),
142-
weekly_dollars: Number(metric.weekly_dollars),
143-
})
144-
}
145-
})
146-
147-
// Create a map of aggregated usage metrics by publisher/agent_name
148-
const metricsMap = new Map()
149-
usageMetrics.forEach((metric) => {
150-
if (metric.publisher_id && metric.agent_name) {
151-
const key = `${metric.publisher_id}/${metric.agent_name}`
152-
const weeklyData = weeklyMap.get(key) || {
153-
weekly_runs: 0,
154-
weekly_dollars: 0,
155-
}
156-
metricsMap.set(key, {
157-
weekly_runs: weeklyData.weekly_runs,
158-
weekly_dollars: weeklyData.weekly_dollars,
159-
total_dollars: Number(metric.total_dollars),
160-
total_invocations: Number(metric.total_invocations),
161-
avg_cost_per_run: Number(metric.avg_cost_per_run),
162-
unique_users: Number(metric.unique_users),
163-
last_used: metric.last_used,
164-
})
165-
}
166-
})
167-
168-
// Create per-version weekly metrics map
169-
const perVersionWeeklyMap = new Map()
170-
perVersionWeeklyMetrics.forEach((metric) => {
171-
if (metric.publisher_id && metric.agent_name && metric.agent_version) {
172-
const key = `${metric.publisher_id}/${metric.agent_name}@${metric.agent_version}`
173-
perVersionWeeklyMap.set(key, {
174-
weekly_runs: Number(metric.weekly_runs),
175-
weekly_dollars: Number(metric.weekly_dollars),
176-
})
177-
}
178-
})
179-
180-
// Create per-version metrics map
181-
const perVersionMetricsMap = new Map()
182-
perVersionMetrics.forEach((metric) => {
183-
if (metric.publisher_id && metric.agent_name && metric.agent_version) {
184-
const key = `${metric.publisher_id}/${metric.agent_name}@${metric.agent_version}`
185-
const weeklyData = perVersionWeeklyMap.get(key) || {
186-
weekly_runs: 0,
187-
weekly_dollars: 0,
188-
}
189-
perVersionMetricsMap.set(key, {
190-
weekly_runs: weeklyData.weekly_runs,
191-
weekly_dollars: weeklyData.weekly_dollars,
192-
total_dollars: Number(metric.total_dollars),
193-
total_invocations: Number(metric.total_invocations),
194-
avg_cost_per_run: Number(metric.avg_cost_per_run),
195-
unique_users: Number(metric.unique_users),
196-
last_used: metric.last_used,
197-
})
198-
}
199-
})
200-
201-
// Group per-version metrics by agent
202-
const versionMetricsByAgent = new Map()
203-
perVersionMetricsMap.forEach((metrics, key) => {
204-
const [publisherAgentKey, version] = key.split('@')
205-
if (!versionMetricsByAgent.has(publisherAgentKey)) {
206-
versionMetricsByAgent.set(publisherAgentKey, {})
207-
}
208-
versionMetricsByAgent.get(publisherAgentKey)[version] = metrics
209-
})
210-
211-
// First, group agents by publisher/name to get the latest version of each
212-
const latestAgents = new Map()
213-
agents.forEach((agent) => {
214-
const agentData =
215-
typeof agent.data === 'string' ? JSON.parse(agent.data) : agent.data
216-
const agentName = agentData.name || agent.id
217-
const key = `${agent.publisher.id}/${agentName}`
218-
219-
if (!latestAgents.has(key)) {
220-
latestAgents.set(key, {
221-
agent,
222-
agentData,
223-
agentName,
224-
})
225-
}
226-
})
227-
228-
// Transform the latest agents with their aggregated metrics
229-
const result = Array.from(latestAgents.values()).map(
230-
({ agent, agentData, agentName }) => {
231-
const agentKey = `${agent.publisher.id}/${agentName}`
232-
const metrics = metricsMap.get(agentKey) || {
233-
weekly_runs: 0,
234-
weekly_dollars: 0,
235-
total_dollars: 0,
236-
total_invocations: 0,
237-
avg_cost_per_run: 0,
238-
unique_users: 0,
239-
last_used: null,
240-
}
241-
242-
// Use agent.id (config ID) to get version stats since that's what the runs table uses as agent_name
243-
const versionStatsKey = `${agent.publisher.id}/${agent.id}`
244-
const version_stats = versionMetricsByAgent.get(versionStatsKey) || {}
245-
246-
return {
247-
id: agent.id,
248-
name: agentName,
249-
description: agentData.description,
250-
publisher: agent.publisher,
251-
version: agent.version,
252-
created_at: agent.created_at,
253-
// Aggregated stats across all versions (for agent store)
254-
usage_count: metrics.total_invocations,
255-
weekly_runs: metrics.weekly_runs,
256-
weekly_spent: metrics.weekly_dollars,
257-
total_spent: metrics.total_dollars,
258-
avg_cost_per_invocation: metrics.avg_cost_per_run,
259-
unique_users: metrics.unique_users,
260-
last_used: metrics.last_used,
261-
// Per-version stats for agent detail pages
262-
version_stats,
263-
tags: agentData.tags || [],
264-
}
265-
},
266-
)
267-
268-
// Sort by weekly usage (most prominent metric)
269-
result.sort((a, b) => (b.weekly_spent || 0) - (a.weekly_spent || 0))
270-
271-
return result
272-
},
273-
['agents-data'],
274-
{
275-
revalidate: 600, // 10 minutes
276-
tags: ['agents', 'api'],
277-
},
278-
)
279-
28010
export async function GET() {
28111
try {
28212
const result = await getCachedAgents()

web/src/app/store/agents-data.ts

Lines changed: 18 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -1,72 +1,28 @@
1-
import { env } from '@codebuff/common/env'
2-
import { unstable_cache } from 'next/cache'
1+
import { getCachedAgents, type AgentData } from '@/server/agents-data'
32

4-
// Types
5-
interface AgentData {
6-
id: string
7-
name: string
8-
description?: string
9-
publisher: {
10-
id: string
11-
name: string
12-
verified: boolean
13-
avatar_url?: string | null
14-
}
15-
version: string
16-
created_at: string
17-
usage_count?: number
18-
weekly_runs?: number
19-
weekly_spent?: number
20-
total_spent?: number
21-
avg_cost_per_invocation?: number
22-
unique_users?: number
23-
last_used?: string
24-
version_stats?: Record<string, any>
25-
tags?: string[]
26-
}
27-
28-
// Server-side data fetching function with ISR
29-
export const getAgentsData = unstable_cache(
30-
async (): Promise<AgentData[]> => {
31-
const baseUrl = env.NEXT_PUBLIC_CODEBUFF_APP_URL || 'http://localhost:3000'
32-
33-
try {
34-
const response = await fetch(`${baseUrl}/api/agents`, {
35-
headers: {
36-
'User-Agent': 'Codebuff-Store-Static',
37-
},
38-
// Configure fetch-level caching
39-
next: {
40-
revalidate: 600, // 10 minutes
41-
tags: ['agents', 'store'],
42-
},
43-
})
44-
45-
if (!response.ok) {
46-
console.error(
47-
'Failed to fetch agents:',
48-
response.status,
49-
response.statusText,
50-
)
51-
return []
52-
}
3+
export const getAgentsData = async (): Promise<AgentData[]> => {
4+
const suppressErrors =
5+
process.env.NEXT_PHASE === 'phase-production-build' ||
6+
process.env.CI === 'true'
537

54-
return await response.json()
55-
} catch (error) {
8+
try {
9+
return await getCachedAgents()
10+
} catch (error) {
11+
if (!suppressErrors) {
5612
console.error('Error fetching agents data:', error)
57-
return []
5813
}
59-
},
60-
['store-agents-data'],
61-
{
62-
revalidate: 600, // Cache for 10 minutes
63-
tags: ['agents', 'store'],
64-
},
65-
)
6614

67-
// Helper function for on-demand revalidation (can be used in webhooks/admin actions)
15+
if (suppressErrors) {
16+
console.warn('Skipping agents data fetch during static build.')
17+
}
18+
19+
return []
20+
}
21+
}
22+
6823
export async function revalidateAgentsData() {
6924
const { revalidateTag } = await import('next/cache')
7025
revalidateTag('agents')
7126
revalidateTag('store')
27+
revalidateTag('api')
7228
}

0 commit comments

Comments
 (0)