@nordsym/apiclaw 1.5.18 → 1.5.19
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.
- package/convex/http.js.map +1 -1
- package/convex/http.ts +315 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +123 -0
- package/dist/credentials.js.map +1 -1
- package/package.json +2 -2
- package/src/credentials.ts +131 -0
- package/convex/adminActivate.d.ts +0 -3
- package/convex/adminActivate.js +0 -47
- package/convex/adminStats.d.ts +0 -3
- package/convex/adminStats.js +0 -42
- package/convex/agents.d.ts +0 -54
- package/convex/agents.js +0 -499
- package/convex/analytics.d.ts +0 -5
- package/convex/analytics.js +0 -166
- package/convex/billing.d.ts +0 -88
- package/convex/billing.js +0 -655
- package/convex/capabilities.d.ts +0 -9
- package/convex/capabilities.js +0 -145
- package/convex/chains.d.ts +0 -67
- package/convex/chains.js +0 -1042
- package/convex/credits.d.ts +0 -25
- package/convex/credits.js +0 -186
- package/convex/crons.d.ts +0 -3
- package/convex/crons.js +0 -17
- package/convex/directCall.d.ts +0 -72
- package/convex/directCall.js +0 -627
- package/convex/earnProgress.d.ts +0 -58
- package/convex/earnProgress.js +0 -649
- package/convex/email.d.ts +0 -14
- package/convex/email.js +0 -300
- package/convex/feedback.d.ts +0 -7
- package/convex/feedback.js +0 -227
- package/convex/http.d.ts +0 -3
- package/convex/http.js +0 -1106
- package/convex/http.ts.bak +0 -934
- package/convex/logs.d.ts +0 -38
- package/convex/logs.js +0 -487
- package/convex/mou.d.ts +0 -6
- package/convex/mou.js +0 -82
- package/convex/providerKeys.d.ts +0 -31
- package/convex/providerKeys.js +0 -257
- package/convex/providers.d.ts +0 -29
- package/convex/providers.js +0 -756
- package/convex/purchases.d.ts +0 -7
- package/convex/purchases.js +0 -157
- package/convex/ratelimit.d.ts +0 -4
- package/convex/ratelimit.js +0 -91
- package/convex/searchLogs.d.ts +0 -4
- package/convex/searchLogs.js +0 -129
- package/convex/spendAlerts.d.ts +0 -36
- package/convex/spendAlerts.js +0 -380
- package/convex/stripeActions.d.ts +0 -19
- package/convex/stripeActions.js +0 -411
- package/convex/teams.d.ts +0 -21
- package/convex/teams.js +0 -215
- package/convex/telemetry.d.ts +0 -4
- package/convex/telemetry.js +0 -74
- package/convex/usage.d.ts +0 -27
- package/convex/usage.js +0 -229
- package/convex/waitlist.d.ts +0 -4
- package/convex/waitlist.js +0 -49
- package/convex/webhooks.d.ts +0 -12
- package/convex/webhooks.js +0 -410
- package/convex/workspaces.d.ts +0 -29
- package/convex/workspaces.js +0 -880
package/convex/agents.js
DELETED
|
@@ -1,499 +0,0 @@
|
|
|
1
|
-
import { v } from "convex/values";
|
|
2
|
-
import { mutation, query } from "./_generated/server";
|
|
3
|
-
// ============================================
|
|
4
|
-
// AGENT NAME GENERATION
|
|
5
|
-
// ============================================
|
|
6
|
-
const ADJECTIVES = [
|
|
7
|
-
"Crimson", "Azure", "Golden", "Silver", "Obsidian",
|
|
8
|
-
"Emerald", "Sapphire", "Violet", "Amber", "Jade",
|
|
9
|
-
"Scarlet", "Cobalt", "Onyx", "Ruby", "Pearl",
|
|
10
|
-
"Shadow", "Storm", "Frost", "Blaze", "Thunder",
|
|
11
|
-
"Swift", "Silent", "Bright", "Dark", "Wild",
|
|
12
|
-
"Noble", "Fierce", "Cosmic", "Quantum", "Neural",
|
|
13
|
-
];
|
|
14
|
-
const NOUNS = [
|
|
15
|
-
"Phoenix", "Falcon", "Dragon", "Wolf", "Raven",
|
|
16
|
-
"Serpent", "Tiger", "Hawk", "Panther", "Lynx",
|
|
17
|
-
"Cipher", "Vector", "Prism", "Nexus", "Core",
|
|
18
|
-
"Agent", "Oracle", "Sentinel", "Phantom", "Vanguard",
|
|
19
|
-
"Forge", "Spark", "Pulse", "Echo", "Byte",
|
|
20
|
-
"Matrix", "Vertex", "Helix", "Nova", "Zenith",
|
|
21
|
-
];
|
|
22
|
-
function generateAgentName() {
|
|
23
|
-
const adj = ADJECTIVES[Math.floor(Math.random() * ADJECTIVES.length)];
|
|
24
|
-
const noun = NOUNS[Math.floor(Math.random() * NOUNS.length)];
|
|
25
|
-
return `${adj} ${noun}`;
|
|
26
|
-
}
|
|
27
|
-
function generateUUID() {
|
|
28
|
-
return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, (c) => {
|
|
29
|
-
const r = Math.random() * 16 | 0;
|
|
30
|
-
const v = c === 'x' ? r : (r & 0x3 | 0x8);
|
|
31
|
-
return v.toString(16);
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
// ============================================
|
|
35
|
-
// MAIN AGENT QUERIES
|
|
36
|
-
// ============================================
|
|
37
|
-
/**
|
|
38
|
-
* Get main agent info for a workspace
|
|
39
|
-
*/
|
|
40
|
-
export const getMainAgent = query({
|
|
41
|
-
args: { token: v.string() },
|
|
42
|
-
handler: async (ctx, { token }) => {
|
|
43
|
-
const session = await ctx.db
|
|
44
|
-
.query("agentSessions")
|
|
45
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
46
|
-
.first();
|
|
47
|
-
if (!session) {
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
const workspace = await ctx.db.get(session.workspaceId);
|
|
51
|
-
if (!workspace) {
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
return {
|
|
55
|
-
workspaceId: workspace._id,
|
|
56
|
-
email: workspace.email,
|
|
57
|
-
mainAgentId: workspace.mainAgentId || null,
|
|
58
|
-
mainAgentName: workspace.mainAgentName || null,
|
|
59
|
-
aiBackend: workspace.aiBackend || null,
|
|
60
|
-
usageCount: workspace.usageCount,
|
|
61
|
-
createdAt: workspace.createdAt,
|
|
62
|
-
};
|
|
63
|
-
},
|
|
64
|
-
});
|
|
65
|
-
/**
|
|
66
|
-
* Rename the main agent
|
|
67
|
-
*/
|
|
68
|
-
export const renameMainAgent = mutation({
|
|
69
|
-
args: {
|
|
70
|
-
token: v.string(),
|
|
71
|
-
name: v.string(),
|
|
72
|
-
},
|
|
73
|
-
handler: async (ctx, { token, name }) => {
|
|
74
|
-
const session = await ctx.db
|
|
75
|
-
.query("agentSessions")
|
|
76
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
77
|
-
.first();
|
|
78
|
-
if (!session) {
|
|
79
|
-
throw new Error("Invalid session");
|
|
80
|
-
}
|
|
81
|
-
const workspace = await ctx.db.get(session.workspaceId);
|
|
82
|
-
if (!workspace) {
|
|
83
|
-
throw new Error("Workspace not found");
|
|
84
|
-
}
|
|
85
|
-
// Validate name length
|
|
86
|
-
const trimmedName = name.trim();
|
|
87
|
-
if (trimmedName.length < 2 || trimmedName.length > 50) {
|
|
88
|
-
throw new Error("Name must be between 2 and 50 characters");
|
|
89
|
-
}
|
|
90
|
-
await ctx.db.patch(workspace._id, {
|
|
91
|
-
mainAgentName: trimmedName,
|
|
92
|
-
updatedAt: Date.now(),
|
|
93
|
-
});
|
|
94
|
-
return { success: true, name: trimmedName };
|
|
95
|
-
},
|
|
96
|
-
});
|
|
97
|
-
/**
|
|
98
|
-
* Initialize main agent (auto-generate name and ID if not set)
|
|
99
|
-
* Called on first API call
|
|
100
|
-
*/
|
|
101
|
-
export const ensureMainAgent = mutation({
|
|
102
|
-
args: { workspaceId: v.id("workspaces") },
|
|
103
|
-
handler: async (ctx, { workspaceId }) => {
|
|
104
|
-
const workspace = await ctx.db.get(workspaceId);
|
|
105
|
-
if (!workspace) {
|
|
106
|
-
throw new Error("Workspace not found");
|
|
107
|
-
}
|
|
108
|
-
// Already initialized
|
|
109
|
-
if (workspace.mainAgentId && workspace.mainAgentName) {
|
|
110
|
-
return {
|
|
111
|
-
mainAgentId: workspace.mainAgentId,
|
|
112
|
-
mainAgentName: workspace.mainAgentName,
|
|
113
|
-
created: false,
|
|
114
|
-
};
|
|
115
|
-
}
|
|
116
|
-
const mainAgentId = workspace.mainAgentId || generateUUID();
|
|
117
|
-
const mainAgentName = workspace.mainAgentName || generateAgentName();
|
|
118
|
-
await ctx.db.patch(workspaceId, {
|
|
119
|
-
mainAgentId,
|
|
120
|
-
mainAgentName,
|
|
121
|
-
updatedAt: Date.now(),
|
|
122
|
-
});
|
|
123
|
-
return {
|
|
124
|
-
mainAgentId,
|
|
125
|
-
mainAgentName,
|
|
126
|
-
created: true,
|
|
127
|
-
};
|
|
128
|
-
},
|
|
129
|
-
});
|
|
130
|
-
// ============================================
|
|
131
|
-
// SUBAGENT QUERIES
|
|
132
|
-
// ============================================
|
|
133
|
-
/**
|
|
134
|
-
* Get all subagents for a workspace
|
|
135
|
-
*/
|
|
136
|
-
export const getSubagents = query({
|
|
137
|
-
args: {
|
|
138
|
-
token: v.string(),
|
|
139
|
-
limit: v.optional(v.number()),
|
|
140
|
-
},
|
|
141
|
-
handler: async (ctx, { token, limit = 50 }) => {
|
|
142
|
-
const session = await ctx.db
|
|
143
|
-
.query("agentSessions")
|
|
144
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
145
|
-
.first();
|
|
146
|
-
if (!session) {
|
|
147
|
-
return { subagents: [], total: 0 };
|
|
148
|
-
}
|
|
149
|
-
const subagents = await ctx.db
|
|
150
|
-
.query("subagents")
|
|
151
|
-
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
|
|
152
|
-
.order("desc")
|
|
153
|
-
.take(limit);
|
|
154
|
-
// Sort by lastActiveAt descending
|
|
155
|
-
const sorted = subagents.sort((a, b) => b.lastActiveAt - a.lastActiveAt);
|
|
156
|
-
return {
|
|
157
|
-
subagents: sorted.map((s) => ({
|
|
158
|
-
id: s._id,
|
|
159
|
-
subagentId: s.subagentId,
|
|
160
|
-
name: s.name || s.subagentId,
|
|
161
|
-
callCount: s.callCount,
|
|
162
|
-
firstSeenAt: s.firstSeenAt,
|
|
163
|
-
lastActiveAt: s.lastActiveAt,
|
|
164
|
-
})),
|
|
165
|
-
total: subagents.length,
|
|
166
|
-
};
|
|
167
|
-
},
|
|
168
|
-
});
|
|
169
|
-
/**
|
|
170
|
-
* Get stats for a specific subagent
|
|
171
|
-
*/
|
|
172
|
-
export const getSubagentStats = query({
|
|
173
|
-
args: {
|
|
174
|
-
token: v.string(),
|
|
175
|
-
subagentId: v.string(),
|
|
176
|
-
},
|
|
177
|
-
handler: async (ctx, { token, subagentId }) => {
|
|
178
|
-
const session = await ctx.db
|
|
179
|
-
.query("agentSessions")
|
|
180
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
181
|
-
.first();
|
|
182
|
-
if (!session) {
|
|
183
|
-
return null;
|
|
184
|
-
}
|
|
185
|
-
const subagent = await ctx.db
|
|
186
|
-
.query("subagents")
|
|
187
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
|
|
188
|
-
.first();
|
|
189
|
-
if (!subagent) {
|
|
190
|
-
return null;
|
|
191
|
-
}
|
|
192
|
-
// Get recent logs for this subagent
|
|
193
|
-
const logs = await ctx.db
|
|
194
|
-
.query("apiLogs")
|
|
195
|
-
.withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
|
|
196
|
-
.order("desc")
|
|
197
|
-
.take(100);
|
|
198
|
-
const successCount = logs.filter((l) => l.status === "success").length;
|
|
199
|
-
const errorCount = logs.filter((l) => l.status === "error").length;
|
|
200
|
-
const avgLatency = logs.length > 0
|
|
201
|
-
? Math.round(logs.reduce((sum, l) => sum + l.latencyMs, 0) / logs.length)
|
|
202
|
-
: 0;
|
|
203
|
-
// Group by provider
|
|
204
|
-
const byProvider = {};
|
|
205
|
-
for (const log of logs) {
|
|
206
|
-
byProvider[log.provider] = (byProvider[log.provider] || 0) + 1;
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
subagentId: subagent.subagentId,
|
|
210
|
-
name: subagent.name || subagent.subagentId,
|
|
211
|
-
callCount: subagent.callCount,
|
|
212
|
-
successCount,
|
|
213
|
-
errorCount,
|
|
214
|
-
successRate: logs.length > 0 ? Math.round((successCount / logs.length) * 100) : 0,
|
|
215
|
-
avgLatency,
|
|
216
|
-
firstSeenAt: subagent.firstSeenAt,
|
|
217
|
-
lastActiveAt: subagent.lastActiveAt,
|
|
218
|
-
byProvider: Object.entries(byProvider)
|
|
219
|
-
.map(([provider, count]) => ({ provider, count }))
|
|
220
|
-
.sort((a, b) => b.count - a.count),
|
|
221
|
-
};
|
|
222
|
-
},
|
|
223
|
-
});
|
|
224
|
-
/**
|
|
225
|
-
* Rename a subagent
|
|
226
|
-
*/
|
|
227
|
-
export const renameSubagent = mutation({
|
|
228
|
-
args: {
|
|
229
|
-
token: v.string(),
|
|
230
|
-
subagentId: v.string(),
|
|
231
|
-
name: v.string(),
|
|
232
|
-
},
|
|
233
|
-
handler: async (ctx, { token, subagentId, name }) => {
|
|
234
|
-
const session = await ctx.db
|
|
235
|
-
.query("agentSessions")
|
|
236
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
237
|
-
.first();
|
|
238
|
-
if (!session) {
|
|
239
|
-
throw new Error("Invalid session");
|
|
240
|
-
}
|
|
241
|
-
const subagent = await ctx.db
|
|
242
|
-
.query("subagents")
|
|
243
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
|
|
244
|
-
.first();
|
|
245
|
-
if (!subagent) {
|
|
246
|
-
throw new Error("Subagent not found");
|
|
247
|
-
}
|
|
248
|
-
const trimmedName = name.trim();
|
|
249
|
-
if (trimmedName.length < 1 || trimmedName.length > 100) {
|
|
250
|
-
throw new Error("Name must be between 1 and 100 characters");
|
|
251
|
-
}
|
|
252
|
-
await ctx.db.patch(subagent._id, { name: trimmedName });
|
|
253
|
-
return { success: true, name: trimmedName };
|
|
254
|
-
},
|
|
255
|
-
});
|
|
256
|
-
/**
|
|
257
|
-
* Track a subagent call (upsert subagent record)
|
|
258
|
-
* Called when X-APIClaw-Subagent header is present
|
|
259
|
-
*/
|
|
260
|
-
export const trackSubagentCall = mutation({
|
|
261
|
-
args: {
|
|
262
|
-
workspaceId: v.id("workspaces"),
|
|
263
|
-
subagentId: v.string(),
|
|
264
|
-
},
|
|
265
|
-
handler: async (ctx, { workspaceId, subagentId }) => {
|
|
266
|
-
const now = Date.now();
|
|
267
|
-
// Find existing subagent record
|
|
268
|
-
const existing = await ctx.db
|
|
269
|
-
.query("subagents")
|
|
270
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
|
|
271
|
-
.first();
|
|
272
|
-
if (existing) {
|
|
273
|
-
// Increment call count
|
|
274
|
-
await ctx.db.patch(existing._id, {
|
|
275
|
-
callCount: existing.callCount + 1,
|
|
276
|
-
lastActiveAt: now,
|
|
277
|
-
});
|
|
278
|
-
return { id: existing._id, created: false };
|
|
279
|
-
}
|
|
280
|
-
// Create new subagent record
|
|
281
|
-
const id = await ctx.db.insert("subagents", {
|
|
282
|
-
workspaceId,
|
|
283
|
-
subagentId,
|
|
284
|
-
callCount: 1,
|
|
285
|
-
firstSeenAt: now,
|
|
286
|
-
lastActiveAt: now,
|
|
287
|
-
});
|
|
288
|
-
return { id, created: true };
|
|
289
|
-
},
|
|
290
|
-
});
|
|
291
|
-
// ============================================
|
|
292
|
-
// AGGREGATE STATS
|
|
293
|
-
// ============================================
|
|
294
|
-
// ============================================
|
|
295
|
-
// AGENT REGISTRATION & AI BACKEND TRACKING
|
|
296
|
-
// ============================================
|
|
297
|
-
/**
|
|
298
|
-
* Pre-register a task agent (subagent)
|
|
299
|
-
* Allows agents to be registered before they make their first call
|
|
300
|
-
*/
|
|
301
|
-
export const registerTaskAgent = mutation({
|
|
302
|
-
args: {
|
|
303
|
-
token: v.string(),
|
|
304
|
-
subagentId: v.string(),
|
|
305
|
-
name: v.optional(v.string()),
|
|
306
|
-
description: v.optional(v.string()),
|
|
307
|
-
},
|
|
308
|
-
handler: async (ctx, { token, subagentId, name, description }) => {
|
|
309
|
-
const session = await ctx.db
|
|
310
|
-
.query("agentSessions")
|
|
311
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
312
|
-
.first();
|
|
313
|
-
if (!session) {
|
|
314
|
-
throw new Error("Invalid session");
|
|
315
|
-
}
|
|
316
|
-
// Validate subagentId
|
|
317
|
-
const trimmedId = subagentId.trim();
|
|
318
|
-
if (trimmedId.length < 1 || trimmedId.length > 100) {
|
|
319
|
-
throw new Error("Subagent ID must be between 1 and 100 characters");
|
|
320
|
-
}
|
|
321
|
-
const now = Date.now();
|
|
322
|
-
// Check if already exists
|
|
323
|
-
const existing = await ctx.db
|
|
324
|
-
.query("subagents")
|
|
325
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", trimmedId))
|
|
326
|
-
.first();
|
|
327
|
-
if (existing) {
|
|
328
|
-
// Update existing record
|
|
329
|
-
await ctx.db.patch(existing._id, {
|
|
330
|
-
name: name || existing.name,
|
|
331
|
-
description: description || existing.description,
|
|
332
|
-
isRegistered: true,
|
|
333
|
-
lastActiveAt: now,
|
|
334
|
-
});
|
|
335
|
-
return { id: existing._id, created: false };
|
|
336
|
-
}
|
|
337
|
-
// Create new subagent record
|
|
338
|
-
const id = await ctx.db.insert("subagents", {
|
|
339
|
-
workspaceId: session.workspaceId,
|
|
340
|
-
subagentId: trimmedId,
|
|
341
|
-
name: name,
|
|
342
|
-
description: description,
|
|
343
|
-
callCount: 0,
|
|
344
|
-
isRegistered: true,
|
|
345
|
-
firstSeenAt: now,
|
|
346
|
-
lastActiveAt: now,
|
|
347
|
-
});
|
|
348
|
-
return { id, created: true };
|
|
349
|
-
},
|
|
350
|
-
});
|
|
351
|
-
/**
|
|
352
|
-
* Update AI backend for workspace or subagent
|
|
353
|
-
* Called when X-APIClaw-AI-Backend header is present
|
|
354
|
-
*/
|
|
355
|
-
export const updateAIBackend = mutation({
|
|
356
|
-
args: {
|
|
357
|
-
workspaceId: v.id("workspaces"),
|
|
358
|
-
subagentId: v.optional(v.string()),
|
|
359
|
-
aiBackend: v.string(),
|
|
360
|
-
},
|
|
361
|
-
handler: async (ctx, { workspaceId, subagentId, aiBackend }) => {
|
|
362
|
-
const now = Date.now();
|
|
363
|
-
if (subagentId) {
|
|
364
|
-
// Update subagent's AI backend
|
|
365
|
-
const subagent = await ctx.db
|
|
366
|
-
.query("subagents")
|
|
367
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
|
|
368
|
-
.first();
|
|
369
|
-
if (subagent) {
|
|
370
|
-
await ctx.db.patch(subagent._id, {
|
|
371
|
-
aiBackend,
|
|
372
|
-
lastActiveAt: now,
|
|
373
|
-
});
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
else {
|
|
377
|
-
// Update workspace's main agent AI backend
|
|
378
|
-
await ctx.db.patch(workspaceId, {
|
|
379
|
-
aiBackend,
|
|
380
|
-
aiBackendLastSeen: now,
|
|
381
|
-
updatedAt: now,
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
return { success: true };
|
|
385
|
-
},
|
|
386
|
-
});
|
|
387
|
-
// ============================================
|
|
388
|
-
// AGGREGATE STATS
|
|
389
|
-
// ============================================
|
|
390
|
-
/**
|
|
391
|
-
* Get agent overview for workspace (main + subagents summary)
|
|
392
|
-
*/
|
|
393
|
-
export const getAgentOverview = query({
|
|
394
|
-
args: { token: v.string() },
|
|
395
|
-
handler: async (ctx, { token }) => {
|
|
396
|
-
const session = await ctx.db
|
|
397
|
-
.query("agentSessions")
|
|
398
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
399
|
-
.first();
|
|
400
|
-
if (!session) {
|
|
401
|
-
return null;
|
|
402
|
-
}
|
|
403
|
-
const workspace = await ctx.db.get(session.workspaceId);
|
|
404
|
-
if (!workspace) {
|
|
405
|
-
return null;
|
|
406
|
-
}
|
|
407
|
-
// Get all subagents
|
|
408
|
-
const subagents = await ctx.db
|
|
409
|
-
.query("subagents")
|
|
410
|
-
.withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
|
|
411
|
-
.collect();
|
|
412
|
-
// Calculate totals
|
|
413
|
-
const totalSubagentCalls = subagents.reduce((sum, s) => sum + s.callCount, 0);
|
|
414
|
-
const mainAgentCalls = workspace.usageCount - totalSubagentCalls;
|
|
415
|
-
// Get most active subagents (top 5)
|
|
416
|
-
const topSubagents = subagents
|
|
417
|
-
.sort((a, b) => b.callCount - a.callCount)
|
|
418
|
-
.slice(0, 5)
|
|
419
|
-
.map((s) => ({
|
|
420
|
-
subagentId: s.subagentId,
|
|
421
|
-
name: s.name || s.subagentId,
|
|
422
|
-
callCount: s.callCount,
|
|
423
|
-
lastActiveAt: s.lastActiveAt,
|
|
424
|
-
}));
|
|
425
|
-
return {
|
|
426
|
-
mainAgent: {
|
|
427
|
-
id: workspace.mainAgentId || null,
|
|
428
|
-
name: workspace.mainAgentName || "Unnamed Agent",
|
|
429
|
-
callCount: Math.max(0, mainAgentCalls), // Ensure non-negative
|
|
430
|
-
},
|
|
431
|
-
subagents: {
|
|
432
|
-
total: subagents.length,
|
|
433
|
-
totalCalls: totalSubagentCalls,
|
|
434
|
-
topActive: topSubagents,
|
|
435
|
-
},
|
|
436
|
-
totalCalls: workspace.usageCount,
|
|
437
|
-
};
|
|
438
|
-
},
|
|
439
|
-
});
|
|
440
|
-
/**
|
|
441
|
-
* Delete a subagent
|
|
442
|
-
*/
|
|
443
|
-
export const deleteSubagent = mutation({
|
|
444
|
-
args: {
|
|
445
|
-
token: v.string(),
|
|
446
|
-
subagentId: v.string(),
|
|
447
|
-
},
|
|
448
|
-
handler: async (ctx, { token, subagentId }) => {
|
|
449
|
-
const session = await ctx.db
|
|
450
|
-
.query("agentSessions")
|
|
451
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
452
|
-
.first();
|
|
453
|
-
if (!session) {
|
|
454
|
-
throw new Error("Invalid session");
|
|
455
|
-
}
|
|
456
|
-
const subagent = await ctx.db
|
|
457
|
-
.query("subagents")
|
|
458
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
|
|
459
|
-
.first();
|
|
460
|
-
if (!subagent) {
|
|
461
|
-
throw new Error("Subagent not found");
|
|
462
|
-
}
|
|
463
|
-
await ctx.db.delete(subagent._id);
|
|
464
|
-
return { success: true };
|
|
465
|
-
},
|
|
466
|
-
});
|
|
467
|
-
/**
|
|
468
|
-
* Update subagent stats (call count, last active)
|
|
469
|
-
* Internal helper for tracking
|
|
470
|
-
*/
|
|
471
|
-
export const updateSubagentStats = mutation({
|
|
472
|
-
args: {
|
|
473
|
-
token: v.string(),
|
|
474
|
-
subagentId: v.string(),
|
|
475
|
-
incrementCalls: v.optional(v.number()),
|
|
476
|
-
},
|
|
477
|
-
handler: async (ctx, { token, subagentId, incrementCalls = 1 }) => {
|
|
478
|
-
const session = await ctx.db
|
|
479
|
-
.query("agentSessions")
|
|
480
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
|
|
481
|
-
.first();
|
|
482
|
-
if (!session) {
|
|
483
|
-
throw new Error("Invalid session");
|
|
484
|
-
}
|
|
485
|
-
const subagent = await ctx.db
|
|
486
|
-
.query("subagents")
|
|
487
|
-
.withIndex("by_workspaceId_subagentId", (q) => q.eq("workspaceId", session.workspaceId).eq("subagentId", subagentId))
|
|
488
|
-
.first();
|
|
489
|
-
if (!subagent) {
|
|
490
|
-
throw new Error("Subagent not found");
|
|
491
|
-
}
|
|
492
|
-
await ctx.db.patch(subagent._id, {
|
|
493
|
-
callCount: (subagent.callCount || 0) + incrementCalls,
|
|
494
|
-
lastActiveAt: Date.now(),
|
|
495
|
-
});
|
|
496
|
-
return { success: true, newCallCount: (subagent.callCount || 0) + incrementCalls };
|
|
497
|
-
},
|
|
498
|
-
});
|
|
499
|
-
//# sourceMappingURL=agents.js.map
|
package/convex/analytics.d.ts
DELETED
package/convex/analytics.js
DELETED
|
@@ -1,166 +0,0 @@
|
|
|
1
|
-
import { mutation, query } from "./_generated/server";
|
|
2
|
-
import { v } from "convex/values";
|
|
3
|
-
// Log an analytics event
|
|
4
|
-
export const log = mutation({
|
|
5
|
-
args: {
|
|
6
|
-
event: v.string(),
|
|
7
|
-
provider: v.optional(v.string()),
|
|
8
|
-
query: v.optional(v.string()),
|
|
9
|
-
identifier: v.string(),
|
|
10
|
-
metadata: v.optional(v.any()),
|
|
11
|
-
},
|
|
12
|
-
handler: async (ctx, args) => {
|
|
13
|
-
return await ctx.db.insert("analytics", {
|
|
14
|
-
...args,
|
|
15
|
-
timestamp: Date.now(),
|
|
16
|
-
});
|
|
17
|
-
},
|
|
18
|
-
});
|
|
19
|
-
// Get stats for dashboard
|
|
20
|
-
export const getStats = query({
|
|
21
|
-
args: {
|
|
22
|
-
hoursBack: v.optional(v.number()),
|
|
23
|
-
},
|
|
24
|
-
handler: async (ctx, args) => {
|
|
25
|
-
const hoursBack = args.hoursBack || 24;
|
|
26
|
-
const since = Date.now() - hoursBack * 3600000;
|
|
27
|
-
const events = await ctx.db
|
|
28
|
-
.query("analytics")
|
|
29
|
-
.withIndex("by_timestamp")
|
|
30
|
-
.filter((q) => q.gte(q.field("timestamp"), since))
|
|
31
|
-
.collect();
|
|
32
|
-
// Aggregate stats
|
|
33
|
-
const stats = {
|
|
34
|
-
totalEvents: events.length,
|
|
35
|
-
discoveries: events.filter((e) => e.event === "discovery").length,
|
|
36
|
-
instantCalls: events.filter((e) => e.event === "instant").length,
|
|
37
|
-
uniqueUsers: new Set(events.map((e) => e.identifier)).size,
|
|
38
|
-
byProvider: {},
|
|
39
|
-
topQueries: [],
|
|
40
|
-
hourly: [],
|
|
41
|
-
};
|
|
42
|
-
// By provider
|
|
43
|
-
for (const event of events.filter((e) => e.provider)) {
|
|
44
|
-
stats.byProvider[event.provider] = (stats.byProvider[event.provider] || 0) + 1;
|
|
45
|
-
}
|
|
46
|
-
// Top queries
|
|
47
|
-
const queryCounts = {};
|
|
48
|
-
for (const event of events.filter((e) => e.query)) {
|
|
49
|
-
queryCounts[event.query] = (queryCounts[event.query] || 0) + 1;
|
|
50
|
-
}
|
|
51
|
-
stats.topQueries = Object.entries(queryCounts)
|
|
52
|
-
.sort(([, a], [, b]) => b - a)
|
|
53
|
-
.slice(0, 10)
|
|
54
|
-
.map(([query, count]) => ({ query, count }));
|
|
55
|
-
// Hourly breakdown
|
|
56
|
-
const hourlyCounts = {};
|
|
57
|
-
for (const event of events) {
|
|
58
|
-
const hour = new Date(event.timestamp).toISOString().slice(0, 13);
|
|
59
|
-
hourlyCounts[hour] = (hourlyCounts[hour] || 0) + 1;
|
|
60
|
-
}
|
|
61
|
-
stats.hourly = Object.entries(hourlyCounts)
|
|
62
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
63
|
-
.map(([hour, count]) => ({ hour, count }));
|
|
64
|
-
return stats;
|
|
65
|
-
},
|
|
66
|
-
});
|
|
67
|
-
// Get recent events for live feed
|
|
68
|
-
export const getRecent = query({
|
|
69
|
-
args: {
|
|
70
|
-
limit: v.optional(v.number()),
|
|
71
|
-
},
|
|
72
|
-
handler: async (ctx, args) => {
|
|
73
|
-
const limit = args.limit || 50;
|
|
74
|
-
return await ctx.db
|
|
75
|
-
.query("analytics")
|
|
76
|
-
.withIndex("by_timestamp")
|
|
77
|
-
.order("desc")
|
|
78
|
-
.take(limit);
|
|
79
|
-
},
|
|
80
|
-
});
|
|
81
|
-
// Get provider breakdown for Agent Analytics (workspace-specific)
|
|
82
|
-
export const getProviderBreakdown = query({
|
|
83
|
-
args: {
|
|
84
|
-
token: v.string(),
|
|
85
|
-
periodDays: v.optional(v.number()),
|
|
86
|
-
},
|
|
87
|
-
handler: async (ctx, args) => {
|
|
88
|
-
const periodDays = args.periodDays || 7;
|
|
89
|
-
const since = Date.now() - periodDays * 24 * 3600000;
|
|
90
|
-
// Verify session and get workspace
|
|
91
|
-
const session = await ctx.db
|
|
92
|
-
.query("agentSessions")
|
|
93
|
-
.withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
|
|
94
|
-
.first();
|
|
95
|
-
if (!session) {
|
|
96
|
-
return null;
|
|
97
|
-
}
|
|
98
|
-
// Get all API logs for this workspace
|
|
99
|
-
const logs = await ctx.db
|
|
100
|
-
.query("apiLogs")
|
|
101
|
-
.withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
|
|
102
|
-
.filter((q) => q.gte(q.field("createdAt"), since))
|
|
103
|
-
.collect();
|
|
104
|
-
if (logs.length === 0) {
|
|
105
|
-
return null; // Return null so frontend knows to show empty state, not preview
|
|
106
|
-
}
|
|
107
|
-
// Aggregate stats
|
|
108
|
-
const totalCalls = logs.length;
|
|
109
|
-
const successCount = logs.filter((l) => l.status === "success").length;
|
|
110
|
-
const failureCount = logs.filter((l) => l.status === "error").length;
|
|
111
|
-
const totalLatency = logs.reduce((sum, l) => sum + (l.latencyMs || 0), 0);
|
|
112
|
-
const avgLatency = totalCalls > 0 ? Math.round(totalLatency / totalCalls) : 0;
|
|
113
|
-
// Provider breakdown
|
|
114
|
-
const byProvider = {};
|
|
115
|
-
for (const log of logs) {
|
|
116
|
-
if (!byProvider[log.provider]) {
|
|
117
|
-
byProvider[log.provider] = { count: 0, latency: 0 };
|
|
118
|
-
}
|
|
119
|
-
byProvider[log.provider].count++;
|
|
120
|
-
byProvider[log.provider].latency += log.latencyMs || 0;
|
|
121
|
-
}
|
|
122
|
-
// Agent breakdown (by subagentId)
|
|
123
|
-
const byAgent = {};
|
|
124
|
-
for (const log of logs) {
|
|
125
|
-
const agent = log.subagentId || "main";
|
|
126
|
-
byAgent[agent] = (byAgent[agent] || 0) + 1;
|
|
127
|
-
}
|
|
128
|
-
// Action breakdown
|
|
129
|
-
const byAction = {};
|
|
130
|
-
for (const log of logs) {
|
|
131
|
-
const key = `${log.provider}:${log.action}`;
|
|
132
|
-
byAction[key] = (byAction[key] || 0) + 1;
|
|
133
|
-
}
|
|
134
|
-
// Time series (daily)
|
|
135
|
-
const dailyCounts = {};
|
|
136
|
-
for (const log of logs) {
|
|
137
|
-
const day = new Date(log.createdAt).toISOString().slice(0, 10);
|
|
138
|
-
dailyCounts[day] = (dailyCounts[day] || 0) + 1;
|
|
139
|
-
}
|
|
140
|
-
return {
|
|
141
|
-
totalCalls,
|
|
142
|
-
successCount,
|
|
143
|
-
failureCount,
|
|
144
|
-
successRate: totalCalls > 0 ? (successCount / totalCalls) * 100 : 0,
|
|
145
|
-
avgLatency,
|
|
146
|
-
byProvider: Object.entries(byProvider).map(([name, data]) => ({
|
|
147
|
-
name,
|
|
148
|
-
count: data.count,
|
|
149
|
-
avgLatency: data.count > 0 ? Math.round(data.latency / data.count) : 0,
|
|
150
|
-
})).sort((a, b) => b.count - a.count),
|
|
151
|
-
byAgent: Object.entries(byAgent).map(([name, count]) => ({
|
|
152
|
-
name,
|
|
153
|
-
count,
|
|
154
|
-
})).sort((a, b) => b.count - a.count),
|
|
155
|
-
byAction: Object.entries(byAction).map(([name, count]) => ({
|
|
156
|
-
name,
|
|
157
|
-
count,
|
|
158
|
-
})).sort((a, b) => b.count - a.count).slice(0, 10),
|
|
159
|
-
timeSeries: Object.entries(dailyCounts)
|
|
160
|
-
.sort(([a], [b]) => a.localeCompare(b))
|
|
161
|
-
.map(([date, count]) => ({ date, count })),
|
|
162
|
-
isPreview: false,
|
|
163
|
-
};
|
|
164
|
-
},
|
|
165
|
-
});
|
|
166
|
-
//# sourceMappingURL=analytics.js.map
|