@nordsym/apiclaw 1.7.3 → 1.7.5

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.
Files changed (219) hide show
  1. package/convex/_generated/api.d.ts +115 -0
  2. package/convex/_generated/api.js +23 -0
  3. package/convex/_generated/dataModel.d.ts +60 -0
  4. package/convex/_generated/server.d.ts +143 -0
  5. package/convex/_generated/server.js +93 -0
  6. package/convex/adminActivate.d.ts +3 -0
  7. package/convex/adminActivate.d.ts.map +1 -0
  8. package/convex/adminActivate.js +47 -0
  9. package/convex/adminActivate.js.map +1 -0
  10. package/convex/adminActivate.ts +54 -0
  11. package/convex/adminStats.d.ts +3 -0
  12. package/convex/adminStats.d.ts.map +1 -0
  13. package/convex/adminStats.js +42 -0
  14. package/convex/adminStats.js.map +1 -0
  15. package/convex/adminStats.ts +44 -0
  16. package/convex/agents.d.ts +76 -0
  17. package/convex/agents.d.ts.map +1 -0
  18. package/convex/agents.js +699 -0
  19. package/convex/agents.js.map +1 -0
  20. package/convex/agents.ts +814 -0
  21. package/convex/analytics.d.ts +5 -0
  22. package/convex/analytics.d.ts.map +1 -0
  23. package/convex/analytics.js +166 -0
  24. package/convex/analytics.js.map +1 -0
  25. package/convex/analytics.ts +186 -0
  26. package/convex/billing.d.ts +88 -0
  27. package/convex/billing.d.ts.map +1 -0
  28. package/convex/billing.js +655 -0
  29. package/convex/billing.js.map +1 -0
  30. package/convex/billing.ts +791 -0
  31. package/convex/capabilities.d.ts +9 -0
  32. package/convex/capabilities.d.ts.map +1 -0
  33. package/convex/capabilities.js +145 -0
  34. package/convex/capabilities.js.map +1 -0
  35. package/convex/capabilities.ts +157 -0
  36. package/convex/chains.d.ts +68 -0
  37. package/convex/chains.d.ts.map +1 -0
  38. package/convex/chains.js +1105 -0
  39. package/convex/chains.js.map +1 -0
  40. package/convex/chains.ts +1318 -0
  41. package/convex/credits.d.ts +25 -0
  42. package/convex/credits.d.ts.map +1 -0
  43. package/convex/credits.js +186 -0
  44. package/convex/credits.js.map +1 -0
  45. package/convex/credits.ts +211 -0
  46. package/convex/crons.d.ts +3 -0
  47. package/convex/crons.d.ts.map +1 -0
  48. package/convex/crons.js +17 -0
  49. package/convex/crons.js.map +1 -0
  50. package/convex/crons.ts +28 -0
  51. package/convex/directCall.d.ts +72 -0
  52. package/convex/directCall.d.ts.map +1 -0
  53. package/convex/directCall.js +627 -0
  54. package/convex/directCall.js.map +1 -0
  55. package/convex/directCall.ts +678 -0
  56. package/convex/earnProgress.d.ts +58 -0
  57. package/convex/earnProgress.d.ts.map +1 -0
  58. package/convex/earnProgress.js +649 -0
  59. package/convex/earnProgress.js.map +1 -0
  60. package/convex/earnProgress.ts +753 -0
  61. package/convex/email.d.ts +14 -0
  62. package/convex/email.d.ts.map +1 -0
  63. package/convex/email.js +300 -0
  64. package/convex/email.js.map +1 -0
  65. package/convex/email.ts +329 -0
  66. package/convex/feedback.d.ts +7 -0
  67. package/convex/feedback.d.ts.map +1 -0
  68. package/convex/feedback.js +227 -0
  69. package/convex/feedback.js.map +1 -0
  70. package/convex/feedback.ts +265 -0
  71. package/convex/http.d.ts +3 -0
  72. package/convex/http.d.ts.map +1 -0
  73. package/convex/http.js +1405 -0
  74. package/convex/http.js.map +1 -0
  75. package/convex/http.ts +1577 -0
  76. package/convex/inbound.d.ts +2 -0
  77. package/convex/inbound.d.ts.map +1 -0
  78. package/convex/inbound.js +32 -0
  79. package/convex/inbound.js.map +1 -0
  80. package/convex/inbound.ts +32 -0
  81. package/convex/logs.d.ts +48 -0
  82. package/convex/logs.d.ts.map +1 -0
  83. package/convex/logs.js +592 -0
  84. package/convex/logs.js.map +1 -0
  85. package/convex/logs.ts +662 -0
  86. package/convex/mou.d.ts +6 -0
  87. package/convex/mou.d.ts.map +1 -0
  88. package/convex/mou.js +82 -0
  89. package/convex/mou.js.map +1 -0
  90. package/convex/mou.ts +91 -0
  91. package/convex/providerKeys.d.ts +31 -0
  92. package/convex/providerKeys.d.ts.map +1 -0
  93. package/convex/providerKeys.js +257 -0
  94. package/convex/providerKeys.js.map +1 -0
  95. package/convex/providerKeys.ts +289 -0
  96. package/convex/providers.d.ts +32 -0
  97. package/convex/providers.d.ts.map +1 -0
  98. package/convex/providers.js +814 -0
  99. package/convex/providers.js.map +1 -0
  100. package/convex/providers.ts +909 -0
  101. package/convex/purchases.d.ts +7 -0
  102. package/convex/purchases.d.ts.map +1 -0
  103. package/convex/purchases.js +157 -0
  104. package/convex/purchases.js.map +1 -0
  105. package/convex/purchases.ts +183 -0
  106. package/convex/ratelimit.d.ts +4 -0
  107. package/convex/ratelimit.d.ts.map +1 -0
  108. package/convex/ratelimit.js +91 -0
  109. package/convex/ratelimit.js.map +1 -0
  110. package/convex/ratelimit.ts +104 -0
  111. package/convex/schema.ts +805 -0
  112. package/convex/searchLogs.d.ts +4 -0
  113. package/convex/searchLogs.d.ts.map +1 -0
  114. package/convex/searchLogs.js +129 -0
  115. package/convex/searchLogs.js.map +1 -0
  116. package/convex/searchLogs.ts +146 -0
  117. package/convex/seedAPILayerAPIs.d.ts +7 -0
  118. package/convex/seedAPILayerAPIs.d.ts.map +1 -0
  119. package/convex/seedAPILayerAPIs.js +177 -0
  120. package/convex/seedAPILayerAPIs.js.map +1 -0
  121. package/convex/seedAPILayerAPIs.ts +191 -0
  122. package/convex/seedDirectCallConfigs.d.ts +2 -0
  123. package/convex/seedDirectCallConfigs.d.ts.map +1 -0
  124. package/convex/seedDirectCallConfigs.js +324 -0
  125. package/convex/seedDirectCallConfigs.js.map +1 -0
  126. package/convex/seedDirectCallConfigs.ts +336 -0
  127. package/convex/seedPratham.d.ts +6 -0
  128. package/convex/seedPratham.d.ts.map +1 -0
  129. package/convex/seedPratham.js +150 -0
  130. package/convex/seedPratham.js.map +1 -0
  131. package/convex/seedPratham.ts +161 -0
  132. package/convex/spendAlerts.d.ts +36 -0
  133. package/convex/spendAlerts.d.ts.map +1 -0
  134. package/convex/spendAlerts.js +380 -0
  135. package/convex/spendAlerts.js.map +1 -0
  136. package/convex/spendAlerts.ts +442 -0
  137. package/convex/stripeActions.d.ts +19 -0
  138. package/convex/stripeActions.d.ts.map +1 -0
  139. package/convex/stripeActions.js +411 -0
  140. package/convex/stripeActions.js.map +1 -0
  141. package/convex/stripeActions.ts +512 -0
  142. package/convex/teams.d.ts +21 -0
  143. package/convex/teams.d.ts.map +1 -0
  144. package/convex/teams.js +215 -0
  145. package/convex/teams.js.map +1 -0
  146. package/convex/teams.ts +243 -0
  147. package/convex/telemetry.d.ts +4 -0
  148. package/convex/telemetry.d.ts.map +1 -0
  149. package/convex/telemetry.js +74 -0
  150. package/convex/telemetry.js.map +1 -0
  151. package/convex/telemetry.ts +81 -0
  152. package/convex/tsconfig.json +25 -0
  153. package/convex/updateAPIStatus.d.ts +6 -0
  154. package/convex/updateAPIStatus.d.ts.map +1 -0
  155. package/convex/updateAPIStatus.js +40 -0
  156. package/convex/updateAPIStatus.js.map +1 -0
  157. package/convex/updateAPIStatus.ts +45 -0
  158. package/convex/usage.d.ts +27 -0
  159. package/convex/usage.d.ts.map +1 -0
  160. package/convex/usage.js +229 -0
  161. package/convex/usage.js.map +1 -0
  162. package/convex/usage.ts +260 -0
  163. package/convex/waitlist.d.ts +4 -0
  164. package/convex/waitlist.d.ts.map +1 -0
  165. package/convex/waitlist.js +49 -0
  166. package/convex/waitlist.js.map +1 -0
  167. package/convex/waitlist.ts +55 -0
  168. package/convex/webhooks.d.ts +12 -0
  169. package/convex/webhooks.d.ts.map +1 -0
  170. package/convex/webhooks.js +410 -0
  171. package/convex/webhooks.js.map +1 -0
  172. package/convex/webhooks.ts +494 -0
  173. package/convex/workspaces.d.ts +31 -0
  174. package/convex/workspaces.d.ts.map +1 -0
  175. package/convex/workspaces.js +975 -0
  176. package/convex/workspaces.js.map +1 -0
  177. package/convex/workspaces.ts +1130 -0
  178. package/dist/bin.js +0 -0
  179. package/dist/index.js +9 -0
  180. package/dist/index.js.map +1 -1
  181. package/package.json +1 -1
  182. package/src/index.ts +10 -0
  183. package/dist/chain-types.d.ts +0 -187
  184. package/dist/chain-types.d.ts.map +0 -1
  185. package/dist/chain-types.js +0 -33
  186. package/dist/chain-types.js.map +0 -1
  187. package/dist/registry/apis.json.bak +0 -248811
  188. package/dist/src/bin.js +0 -17
  189. package/dist/src/capability-router.js +0 -240
  190. package/dist/src/chainExecutor.js +0 -451
  191. package/dist/src/chainResolver.js +0 -518
  192. package/dist/src/cli/commands/doctor.js +0 -324
  193. package/dist/src/cli/commands/mcp-install.js +0 -255
  194. package/dist/src/cli/commands/restore.js +0 -259
  195. package/dist/src/cli/commands/setup.js +0 -205
  196. package/dist/src/cli/commands/uninstall.js +0 -188
  197. package/dist/src/cli/index.js +0 -111
  198. package/dist/src/cli.js +0 -302
  199. package/dist/src/confirmation.js +0 -240
  200. package/dist/src/credentials.js +0 -357
  201. package/dist/src/credits.js +0 -260
  202. package/dist/src/crypto.js +0 -66
  203. package/dist/src/discovery.js +0 -504
  204. package/dist/src/enterprise/env.js +0 -123
  205. package/dist/src/enterprise/script-generator.js +0 -460
  206. package/dist/src/execute-dynamic.js +0 -473
  207. package/dist/src/execute.js +0 -1727
  208. package/dist/src/index.js +0 -2062
  209. package/dist/src/metered.js +0 -80
  210. package/dist/src/open-apis.js +0 -276
  211. package/dist/src/proxy.js +0 -28
  212. package/dist/src/session.js +0 -86
  213. package/dist/src/stripe.js +0 -407
  214. package/dist/src/telemetry.js +0 -49
  215. package/dist/src/types.js +0 -2
  216. package/dist/src/utils/backup.js +0 -181
  217. package/dist/src/utils/config.js +0 -220
  218. package/dist/src/utils/os.js +0 -105
  219. package/dist/src/utils/paths.js +0 -159
package/convex/logs.ts ADDED
@@ -0,0 +1,662 @@
1
+ import { v } from "convex/values";
2
+ import { mutation, query } from "./_generated/server";
3
+ import { api } from "./_generated/api";
4
+
5
+ // ============================================
6
+ // MUTATIONS
7
+ // ============================================
8
+
9
+ /**
10
+ * Create a log entry for an API call
11
+ * Called after each Direct Call execution
12
+ */
13
+ export const createLog = mutation({
14
+ args: {
15
+ token: v.string(),
16
+ provider: v.string(),
17
+ action: v.string(),
18
+ status: v.union(v.literal("success"), v.literal("error")),
19
+ latencyMs: v.number(),
20
+ errorMessage: v.optional(v.string()),
21
+ subagentId: v.optional(v.string()), // from X-APIClaw-Subagent header
22
+ },
23
+ handler: async (ctx, args) => {
24
+ // Verify session and get workspace
25
+ const session = await ctx.db
26
+ .query("agentSessions")
27
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
28
+ .first();
29
+
30
+ if (!session) {
31
+ throw new Error("Invalid session token");
32
+ }
33
+
34
+ // Create log entry
35
+ return await ctx.db.insert("apiLogs", {
36
+ workspaceId: session.workspaceId,
37
+ sessionToken: args.token,
38
+ subagentId: args.subagentId,
39
+ provider: args.provider,
40
+ action: args.action,
41
+ status: args.status,
42
+ latencyMs: args.latencyMs,
43
+ errorMessage: args.errorMessage,
44
+ createdAt: Date.now(),
45
+ });
46
+ },
47
+ });
48
+
49
+ /**
50
+ * Internal log creation (when workspaceId is already known)
51
+ * Used by execute functions that have already verified the session
52
+ */
53
+ export const createLogInternal = mutation({
54
+ args: {
55
+ workspaceId: v.id("workspaces"),
56
+ sessionToken: v.string(),
57
+ provider: v.string(),
58
+ action: v.string(),
59
+ status: v.union(v.literal("success"), v.literal("error")),
60
+ latencyMs: v.number(),
61
+ errorMessage: v.optional(v.string()),
62
+ subagentId: v.optional(v.string()), // from X-APIClaw-Subagent header
63
+ },
64
+ handler: async (ctx, args) => {
65
+ return await ctx.db.insert("apiLogs", {
66
+ workspaceId: args.workspaceId,
67
+ sessionToken: args.sessionToken,
68
+ subagentId: args.subagentId,
69
+ provider: args.provider,
70
+ action: args.action,
71
+ status: args.status,
72
+ latencyMs: args.latencyMs,
73
+ errorMessage: args.errorMessage,
74
+ createdAt: Date.now(),
75
+ });
76
+ },
77
+ });
78
+
79
+ /**
80
+ * Log an inbound call to a provider's workspace
81
+ * Called when someone uses an API that belongs to another workspace
82
+ */
83
+ export const logProviderCall = mutation({
84
+ args: {
85
+ provider: v.string(),
86
+ action: v.string(),
87
+ status: v.union(v.literal("success"), v.literal("error")),
88
+ latencyMs: v.number(),
89
+ callerWorkspaceId: v.string(),
90
+ subagentId: v.optional(v.string()),
91
+ errorMessage: v.optional(v.string()),
92
+ },
93
+ handler: async (ctx, args) => {
94
+ // Map provider to workspace email
95
+ const providerEmailMap: Record<string, string> = {
96
+ apilayer: "gustav_hemmingsson@hotmail.com",
97
+ };
98
+
99
+ const providerEmail = providerEmailMap[args.provider];
100
+ if (!providerEmail) return null; // no workspace for this provider
101
+
102
+ const workspace = await ctx.db
103
+ .query("workspaces")
104
+ .withIndex("by_email", (q) => q.eq("email", providerEmail))
105
+ .first();
106
+ if (!workspace) return null;
107
+
108
+ return await ctx.db.insert("apiLogs", {
109
+ workspaceId: workspace._id,
110
+ sessionToken: "",
111
+ provider: args.provider,
112
+ action: args.action,
113
+ status: args.status,
114
+ latencyMs: args.latencyMs,
115
+ direction: "inbound",
116
+ callerWorkspaceId: args.callerWorkspaceId,
117
+ subagentId: args.subagentId,
118
+ errorMessage: args.errorMessage,
119
+ createdAt: Date.now(),
120
+ });
121
+ },
122
+ });
123
+
124
+ /**
125
+ * Get analytics for a provider workspace (inbound calls to their APIs)
126
+ * Used by API Analytics tab in workspace dashboard
127
+ */
128
+ export const getProviderAnalytics = query({
129
+ args: {
130
+ token: v.string(),
131
+ hoursBack: v.optional(v.number()),
132
+ },
133
+ handler: async (ctx, { token, hoursBack = 168 }) => {
134
+ const session = await ctx.db
135
+ .query("agentSessions")
136
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
137
+ .first();
138
+ if (!session) return null;
139
+
140
+ const since = Date.now() - hoursBack * 60 * 60 * 1000;
141
+
142
+ // Get all logs for this workspace
143
+ const allLogs = await ctx.db
144
+ .query("apiLogs")
145
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
146
+ .collect();
147
+
148
+ const periodLogs = allLogs.filter((l) => l.createdAt >= since);
149
+ const inboundLogs = periodLogs.filter((l) => l.direction === "inbound");
150
+ const outboundLogs = periodLogs.filter((l) => l.direction !== "inbound");
151
+
152
+ // Daily buckets
153
+ const byDay: Record<string, number> = {};
154
+ periodLogs.forEach((l) => {
155
+ const day = new Date(l.createdAt).toISOString().split("T")[0];
156
+ byDay[day] = (byDay[day] || 0) + 1;
157
+ });
158
+
159
+ // By provider
160
+ const byProvider: Record<string, { calls: number; success: number }> = {};
161
+ periodLogs.forEach((l) => {
162
+ if (!byProvider[l.provider]) byProvider[l.provider] = { calls: 0, success: 0 };
163
+ byProvider[l.provider].calls++;
164
+ if (l.status === "success") byProvider[l.provider].success++;
165
+ });
166
+
167
+ // Unique callers (for inbound)
168
+ const uniqueCallers = new Set(inboundLogs.map((l) => l.callerWorkspaceId).filter(Boolean)).size;
169
+
170
+ return {
171
+ totalCalls: periodLogs.length,
172
+ inboundCalls: inboundLogs.length,
173
+ outboundCalls: outboundLogs.length,
174
+ uniqueCallers,
175
+ byDay: Object.entries(byDay)
176
+ .map(([date, calls]) => ({ date, calls }))
177
+ .sort((a, b) => a.date.localeCompare(b.date)),
178
+ byProvider: Object.entries(byProvider)
179
+ .map(([provider, stats]) => ({ provider, ...stats }))
180
+ .sort((a, b) => b.calls - a.calls),
181
+ successRate: periodLogs.length > 0
182
+ ? (periodLogs.filter((l) => l.status === "success").length / periodLogs.length) * 100
183
+ : 100,
184
+ avgLatency: periodLogs.length > 0
185
+ ? periodLogs.reduce((sum, l) => sum + l.latencyMs, 0) / periodLogs.length
186
+ : 0,
187
+ };
188
+ },
189
+ });
190
+
191
+ // ============================================
192
+ // HELPER: Get month start
193
+ // ============================================
194
+
195
+ function getMonthStart(): number {
196
+ const now = new Date();
197
+ return new Date(now.getUTCFullYear(), now.getUTCMonth(), 1, 0, 0, 0, 0).getTime();
198
+ }
199
+
200
+ /**
201
+ * Combined log creation + spend tracking (PRD 2.6)
202
+ * Creates log entry, tracks spend, returns budget status
203
+ * Returns shouldSendAlert: true if 80% threshold crossed (caller should send email)
204
+ */
205
+ export const createLogWithSpend = mutation({
206
+ args: {
207
+ workspaceId: v.id("workspaces"),
208
+ sessionToken: v.string(),
209
+ provider: v.string(),
210
+ action: v.string(),
211
+ status: v.union(v.literal("success"), v.literal("error")),
212
+ latencyMs: v.number(),
213
+ costCents: v.number(), // Cost in USD cents
214
+ errorMessage: v.optional(v.string()),
215
+ subagentId: v.optional(v.string()),
216
+ },
217
+ handler: async (ctx, args) => {
218
+ const now = Date.now();
219
+ const monthStart = getMonthStart();
220
+
221
+ // 1. Create log entry
222
+ const logId = await ctx.db.insert("apiLogs", {
223
+ workspaceId: args.workspaceId,
224
+ sessionToken: args.sessionToken,
225
+ subagentId: args.subagentId,
226
+ provider: args.provider,
227
+ action: args.action,
228
+ status: args.status,
229
+ latencyMs: args.latencyMs,
230
+ errorMessage: args.errorMessage,
231
+ createdAt: now,
232
+ });
233
+
234
+ // 2. Track spend if successful call with cost
235
+ if (args.status === "success" && args.costCents > 0) {
236
+ const workspace = await ctx.db.get(args.workspaceId);
237
+ if (!workspace) {
238
+ return { logId, spendTracked: false };
239
+ }
240
+
241
+ // Reset monthly spend if new month
242
+ let currentSpend = workspace.monthlySpendCents || 0;
243
+ if (!workspace.lastSpendResetAt || workspace.lastSpendResetAt < monthStart) {
244
+ currentSpend = 0;
245
+ }
246
+
247
+ // Add new spend
248
+ const newSpend = currentSpend + args.costCents;
249
+ const budgetCap = workspace.budgetCap;
250
+
251
+ // Update workspace
252
+ await ctx.db.patch(args.workspaceId, {
253
+ monthlySpendCents: newSpend,
254
+ lastSpendResetAt: monthStart,
255
+ updatedAt: now,
256
+ });
257
+
258
+ // Check if we need to send alert (80% threshold)
259
+ let shouldSendAlert = false;
260
+ let budgetExceeded = false;
261
+
262
+ if (budgetCap && budgetCap > 0) {
263
+ const threshold = budgetCap * 0.8;
264
+ const alertAlreadySentThisMonth = workspace.budgetAlertSentAt &&
265
+ workspace.budgetAlertSentAt >= monthStart;
266
+
267
+ // Check if at 80% and alert not yet sent
268
+ if (newSpend >= threshold && !alertAlreadySentThisMonth) {
269
+ shouldSendAlert = true;
270
+ await ctx.db.patch(args.workspaceId, {
271
+ budgetAlertSentAt: now,
272
+ });
273
+ }
274
+
275
+ // Check if budget exceeded
276
+ if (newSpend >= budgetCap) {
277
+ budgetExceeded = true;
278
+ }
279
+ }
280
+
281
+ return {
282
+ logId,
283
+ spendTracked: true,
284
+ currentSpendCents: newSpend,
285
+ budgetCapCents: budgetCap || null,
286
+ budgetPercentage: budgetCap ? Math.round((newSpend / budgetCap) * 100) : null,
287
+ shouldSendAlert,
288
+ budgetExceeded,
289
+ email: workspace.email,
290
+ };
291
+ }
292
+
293
+ return { logId, spendTracked: false };
294
+ },
295
+ });
296
+
297
+ // ============================================
298
+ // QUERIES
299
+ // ============================================
300
+
301
+ /**
302
+ * Get logs for a workspace with pagination and filters
303
+ */
304
+ export const getLogs = query({
305
+ args: {
306
+ token: v.string(),
307
+ limit: v.optional(v.number()),
308
+ cursor: v.optional(v.number()), // createdAt timestamp for pagination
309
+ status: v.optional(v.union(v.literal("success"), v.literal("error"), v.literal("all"))),
310
+ provider: v.optional(v.string()),
311
+ subagentId: v.optional(v.string()), // filter by subagent
312
+ },
313
+ handler: async (ctx, args) => {
314
+ const limit = args.limit ?? 50;
315
+ const status = args.status ?? "all";
316
+ const provider = args.provider;
317
+ const subagentId = args.subagentId;
318
+ const cursor = args.cursor;
319
+
320
+ // Verify session
321
+ const session = await ctx.db
322
+ .query("agentSessions")
323
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
324
+ .first();
325
+
326
+ if (!session) {
327
+ return { logs: [], hasMore: false };
328
+ }
329
+
330
+ // Get logs for workspace
331
+ let query = ctx.db
332
+ .query("apiLogs")
333
+ .withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
334
+ .order("desc");
335
+
336
+ // Apply cursor (pagination)
337
+ if (cursor) {
338
+ query = query.filter((q) => q.lt(q.field("createdAt"), cursor));
339
+ }
340
+
341
+ // Collect more than limit to check hasMore
342
+ const allLogs = await query.take(limit + 1);
343
+
344
+ // Apply filters in-memory (Convex doesn't support complex compound filters)
345
+ let filteredLogs = allLogs;
346
+
347
+ if (status !== "all") {
348
+ filteredLogs = filteredLogs.filter((log) => log.status === status);
349
+ }
350
+
351
+ if (provider && provider !== "all") {
352
+ filteredLogs = filteredLogs.filter((log) => log.provider === provider);
353
+ }
354
+
355
+ // Filter by subagent
356
+ if (subagentId) {
357
+ if (subagentId === "main") {
358
+ // Main agent calls (no subagentId)
359
+ filteredLogs = filteredLogs.filter((log) => !log.subagentId);
360
+ } else {
361
+ filteredLogs = filteredLogs.filter((log) => log.subagentId === subagentId);
362
+ }
363
+ }
364
+
365
+ const hasMore = filteredLogs.length > limit;
366
+ const logs = filteredLogs.slice(0, limit);
367
+
368
+ return {
369
+ logs: logs.map((log) => ({
370
+ id: log._id,
371
+ provider: log.provider,
372
+ action: log.action,
373
+ status: log.status,
374
+ latencyMs: log.latencyMs,
375
+ errorMessage: log.errorMessage,
376
+ subagentId: log.subagentId || null,
377
+ createdAt: log.createdAt,
378
+ })),
379
+ hasMore,
380
+ nextCursor: logs.length > 0 ? logs[logs.length - 1].createdAt : undefined,
381
+ };
382
+ },
383
+ });
384
+
385
+ /**
386
+ * Get aggregated log stats for workspace
387
+ */
388
+ export const getLogStats = query({
389
+ args: {
390
+ token: v.string(),
391
+ periodDays: v.optional(v.number()),
392
+ },
393
+ handler: async (ctx, args) => {
394
+ const periodDays = args.periodDays ?? 7;
395
+
396
+ // Verify session
397
+ const session = await ctx.db
398
+ .query("agentSessions")
399
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
400
+ .first();
401
+
402
+ if (!session) {
403
+ return {
404
+ totalCalls: 0,
405
+ successCount: 0,
406
+ errorCount: 0,
407
+ successRate: 0,
408
+ avgLatency: 0,
409
+ byProvider: [],
410
+ byDay: [],
411
+ };
412
+ }
413
+
414
+ const now = Date.now();
415
+ const periodStart = now - periodDays * 24 * 60 * 60 * 1000;
416
+
417
+ // Get all logs for this workspace in the period
418
+ const logs = await ctx.db
419
+ .query("apiLogs")
420
+ .withIndex("by_workspaceId_createdAt", (q) => q.eq("workspaceId", session.workspaceId))
421
+ .filter((q) => q.gte(q.field("createdAt"), periodStart))
422
+ .collect();
423
+
424
+ const totalCalls = logs.length;
425
+ const successCount = logs.filter((l) => l.status === "success").length;
426
+ const errorCount = logs.filter((l) => l.status === "error").length;
427
+ const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 0;
428
+ const totalLatency = logs.reduce((sum, l) => sum + l.latencyMs, 0);
429
+ const avgLatency = totalCalls > 0 ? Math.round(totalLatency / totalCalls) : 0;
430
+
431
+ // Group by provider
432
+ const byProviderMap: Record<string, { calls: number; success: number; error: number; latency: number }> = {};
433
+ for (const log of logs) {
434
+ if (!byProviderMap[log.provider]) {
435
+ byProviderMap[log.provider] = { calls: 0, success: 0, error: 0, latency: 0 };
436
+ }
437
+ byProviderMap[log.provider].calls++;
438
+ byProviderMap[log.provider].latency += log.latencyMs;
439
+ if (log.status === "success") {
440
+ byProviderMap[log.provider].success++;
441
+ } else {
442
+ byProviderMap[log.provider].error++;
443
+ }
444
+ }
445
+
446
+ const byProvider = Object.entries(byProviderMap)
447
+ .map(([provider, data]) => ({
448
+ provider,
449
+ calls: data.calls,
450
+ successRate: data.calls > 0 ? (data.success / data.calls) * 100 : 0,
451
+ avgLatency: data.calls > 0 ? Math.round(data.latency / data.calls) : 0,
452
+ }))
453
+ .sort((a, b) => b.calls - a.calls);
454
+
455
+ // Group by day
456
+ const byDayMap: Record<string, { calls: number; success: number; error: number }> = {};
457
+ for (const log of logs) {
458
+ const day = new Date(log.createdAt).toISOString().split("T")[0];
459
+ if (!byDayMap[day]) {
460
+ byDayMap[day] = { calls: 0, success: 0, error: 0 };
461
+ }
462
+ byDayMap[day].calls++;
463
+ if (log.status === "success") {
464
+ byDayMap[day].success++;
465
+ } else {
466
+ byDayMap[day].error++;
467
+ }
468
+ }
469
+
470
+ const byDay = Object.entries(byDayMap)
471
+ .map(([date, data]) => ({
472
+ date,
473
+ calls: data.calls,
474
+ success: data.success,
475
+ error: data.error,
476
+ }))
477
+ .sort((a, b) => a.date.localeCompare(b.date));
478
+
479
+ // Get unique providers and agents for filter dropdowns
480
+ const providers = [...new Set(logs.map((l) => l.provider))].sort();
481
+ const agents = [...new Set(logs.map((l) => l.subagentId || "main"))].sort();
482
+
483
+ return {
484
+ totalCalls,
485
+ successCount,
486
+ errorCount,
487
+ successRate: Math.round(successRate * 10) / 10,
488
+ avgLatency,
489
+ byProvider,
490
+ byDay,
491
+ providers,
492
+ agents,
493
+ };
494
+ },
495
+ });
496
+
497
+ /**
498
+ * Get unique providers for filter dropdown
499
+ */
500
+ export const getProviders = query({
501
+ args: {
502
+ token: v.string(),
503
+ },
504
+ handler: async (ctx, args) => {
505
+ // Verify session
506
+ const session = await ctx.db
507
+ .query("agentSessions")
508
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
509
+ .first();
510
+
511
+ if (!session) {
512
+ return [];
513
+ }
514
+
515
+ // Get all logs for this workspace
516
+ const logs = await ctx.db
517
+ .query("apiLogs")
518
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
519
+ .collect();
520
+
521
+ // Get unique providers
522
+ const providers = [...new Set(logs.map((l) => l.provider))].sort();
523
+ return providers;
524
+ },
525
+ });
526
+
527
+ /**
528
+ * Get logs for a specific subagent
529
+ */
530
+ export const getBySubagent = query({
531
+ args: {
532
+ token: v.string(),
533
+ subagentId: v.string(),
534
+ limit: v.optional(v.number()),
535
+ },
536
+ handler: async (ctx, { token, subagentId, limit = 20 }) => {
537
+ // Verify session
538
+ const session = await ctx.db
539
+ .query("agentSessions")
540
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
541
+ .first();
542
+
543
+ if (!session) return null;
544
+
545
+ // Get API logs for this subagent
546
+ const apiLogs = await ctx.db
547
+ .query("apiLogs")
548
+ .withIndex("by_subagentId", (q) => q.eq("subagentId", subagentId))
549
+ .order("desc")
550
+ .take(limit);
551
+
552
+ // Get search logs for this subagent
553
+ const searchLogs = await ctx.db
554
+ .query("searchLogs")
555
+ .filter((q) => q.eq(q.field("subagentId"), subagentId))
556
+ .order("desc")
557
+ .take(limit);
558
+
559
+ // Merge and sort by timestamp
560
+ const combined = [
561
+ ...apiLogs.map(l => ({
562
+ ...l,
563
+ type: "direct_call" as const,
564
+ timestamp: l.createdAt
565
+ })),
566
+ ...searchLogs.map(l => ({
567
+ ...l,
568
+ type: "search" as const,
569
+ timestamp: l.timestamp
570
+ })),
571
+ ].sort((a, b) => (b.timestamp || 0) - (a.timestamp || 0));
572
+
573
+ return combined.slice(0, limit);
574
+ },
575
+ });
576
+
577
+ /**
578
+ * Clear all logs for a workspace (admin cleanup)
579
+ */
580
+ export const clearWorkspaceLogs = mutation({
581
+ args: {
582
+ token: v.string(),
583
+ },
584
+ handler: async (ctx, { token }) => {
585
+ const session = await ctx.db
586
+ .query("agentSessions")
587
+ .withIndex("by_sessionToken", (q) => q.eq("sessionToken", token))
588
+ .first();
589
+
590
+ if (!session) throw new Error("Invalid session");
591
+
592
+ // Delete all apiLogs
593
+ const apiLogs = await ctx.db
594
+ .query("apiLogs")
595
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
596
+ .collect();
597
+
598
+ for (const log of apiLogs) {
599
+ await ctx.db.delete(log._id);
600
+ }
601
+
602
+ // Delete all searchLogs
603
+ const searchLogs = await ctx.db
604
+ .query("searchLogs")
605
+ .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
606
+ .collect();
607
+
608
+ for (const log of searchLogs) {
609
+ await ctx.db.delete(log._id);
610
+ }
611
+
612
+ return {
613
+ deleted: {
614
+ apiLogs: apiLogs.length,
615
+ searchLogs: searchLogs.length
616
+ }
617
+ };
618
+ },
619
+ });
620
+
621
+ // Log proxy API calls from external agents (Hivr bees)
622
+ export const createProxyLog = mutation({
623
+ args: {
624
+ workspaceId: v.id("workspaces"),
625
+ provider: v.string(),
626
+ action: v.string(),
627
+ subagentId: v.optional(v.string()),
628
+ sessionToken: v.optional(v.string()),
629
+ },
630
+ handler: async (ctx, { workspaceId, provider, action, subagentId, sessionToken }) => {
631
+ const now = Date.now();
632
+
633
+ await ctx.db.insert("apiLogs", {
634
+ workspaceId,
635
+ provider,
636
+ action,
637
+ subagentId: subagentId || "unknown",
638
+ sessionToken: sessionToken || "proxy",
639
+ status: "success",
640
+ latencyMs: 0, // Proxy calls don't track latency
641
+ createdAt: now,
642
+ });
643
+
644
+ // Update workspace lastActiveAt (main agent activity)
645
+ await ctx.db.patch(workspaceId, { lastActiveAt: now });
646
+
647
+ // If this is a subagent call, update that subagent's timestamp
648
+ if (subagentId && subagentId !== "unknown" && subagentId !== "main") {
649
+ const subagent = await ctx.db
650
+ .query("subagents")
651
+ .withIndex("by_workspaceId_subagentId", (q) =>
652
+ q.eq("workspaceId", workspaceId).eq("subagentId", subagentId))
653
+ .first();
654
+
655
+ if (subagent) {
656
+ await ctx.db.patch(subagent._id, { lastActiveAt: now });
657
+ }
658
+ }
659
+
660
+ return { success: true };
661
+ },
662
+ });
@@ -0,0 +1,6 @@
1
+ export declare const getByPartnerId: any;
2
+ export declare const create: any;
3
+ export declare const sign: any;
4
+ export declare const list: any;
5
+ export declare const remove: any;
6
+ //# sourceMappingURL=mou.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"mou.d.ts","sourceRoot":"","sources":["mou.ts"],"names":[],"mappings":"AAIA,eAAO,MAAM,cAAc,KAQzB,CAAC;AAGH,eAAO,MAAM,MAAM,KAcjB,CAAC;AAGH,eAAO,MAAM,IAAI,KAiCf,CAAC;AAGH,eAAO,MAAM,IAAI,KAKf,CAAC;AAGH,eAAO,MAAM,MAAM,KAcjB,CAAC"}