@nordsym/apiclaw 2.1.0 → 2.2.1

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 (185) hide show
  1. package/README.md +15 -2
  2. package/dist/bin-http.js +0 -0
  3. package/dist/bin.bundled.js +79288 -0
  4. package/dist/funnel-client.d.ts +24 -0
  5. package/dist/funnel-client.d.ts.map +1 -0
  6. package/dist/funnel-client.js +131 -0
  7. package/dist/funnel-client.js.map +1 -0
  8. package/dist/funnel.test.d.ts +2 -0
  9. package/dist/funnel.test.d.ts.map +1 -0
  10. package/dist/funnel.test.js +145 -0
  11. package/dist/funnel.test.js.map +1 -0
  12. package/dist/gateway-client.d.ts.map +1 -1
  13. package/dist/gateway-client.js +24 -2
  14. package/dist/gateway-client.js.map +1 -1
  15. package/dist/index.bundled.js +61263 -0
  16. package/dist/index.js +161 -74
  17. package/dist/index.js.map +1 -1
  18. package/dist/postinstall.d.ts +0 -5
  19. package/dist/postinstall.d.ts.map +1 -1
  20. package/dist/postinstall.js +24 -3
  21. package/dist/postinstall.js.map +1 -1
  22. package/dist/registration-guard.d.ts +29 -0
  23. package/dist/registration-guard.d.ts.map +1 -0
  24. package/dist/registration-guard.js +87 -0
  25. package/dist/registration-guard.js.map +1 -0
  26. package/package.json +7 -2
  27. package/.claude/settings.local.json +0 -9
  28. package/.env.prod +0 -1
  29. package/apiclaw-README.md +0 -494
  30. package/convex/_generated/api.d.ts +0 -137
  31. package/convex/_generated/api.js +0 -23
  32. package/convex/_generated/dataModel.d.ts +0 -60
  33. package/convex/_generated/server.d.ts +0 -143
  34. package/convex/_generated/server.js +0 -93
  35. package/convex/adminActivate.ts +0 -53
  36. package/convex/adminStats.ts +0 -306
  37. package/convex/agents.ts +0 -939
  38. package/convex/analytics.ts +0 -187
  39. package/convex/apiKeys.ts +0 -220
  40. package/convex/backfillAnalytics.ts +0 -272
  41. package/convex/backfillSearchLogs.ts +0 -35
  42. package/convex/billing.ts +0 -834
  43. package/convex/capabilities.ts +0 -157
  44. package/convex/chains.ts +0 -1318
  45. package/convex/credits.ts +0 -211
  46. package/convex/crons.ts +0 -50
  47. package/convex/debugFilestackLogs.ts +0 -16
  48. package/convex/debugGetToken.ts +0 -18
  49. package/convex/directCall.ts +0 -713
  50. package/convex/earnProgress.ts +0 -753
  51. package/convex/email.ts +0 -329
  52. package/convex/feedback.ts +0 -265
  53. package/convex/http.ts +0 -3430
  54. package/convex/inbound.ts +0 -32
  55. package/convex/logs.ts +0 -701
  56. package/convex/migrateFilestack.ts +0 -81
  57. package/convex/migratePartnersProd.ts +0 -174
  58. package/convex/migratePratham.ts +0 -126
  59. package/convex/migrateProviderWorkspaces.ts +0 -175
  60. package/convex/mou.ts +0 -91
  61. package/convex/providerKeys.ts +0 -289
  62. package/convex/providers.ts +0 -1135
  63. package/convex/purchases.ts +0 -183
  64. package/convex/ratelimit.ts +0 -104
  65. package/convex/schema.ts +0 -869
  66. package/convex/searchLogs.ts +0 -265
  67. package/convex/seedAPILayerAPIs.ts +0 -191
  68. package/convex/seedDirectCallConfigs.ts +0 -336
  69. package/convex/seedPratham.ts +0 -149
  70. package/convex/spendAlerts.ts +0 -442
  71. package/convex/stripeActions.ts +0 -607
  72. package/convex/teams.ts +0 -243
  73. package/convex/telemetry.ts +0 -81
  74. package/convex/tsconfig.json +0 -25
  75. package/convex/updateAPIStatus.ts +0 -44
  76. package/convex/usage.ts +0 -260
  77. package/convex/usageReports.ts +0 -357
  78. package/convex/waitlist.ts +0 -55
  79. package/convex/webhooks.ts +0 -494
  80. package/convex/workspaceSettings.ts +0 -143
  81. package/convex/workspaces.ts +0 -1331
  82. package/convex.json +0 -3
  83. package/direct-test.mjs +0 -51
  84. package/email-templates/filestack-provider-outreach.html +0 -162
  85. package/email-templates/partnership-template.html +0 -116
  86. package/email-templates/pratham-draft-preview.txt +0 -57
  87. package/email-templates/pratham-partnership-draft.html +0 -141
  88. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  89. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  90. package/reports/pipeline/acquire_apisguru.json +0 -17
  91. package/reports/pipeline/capabilities.json +0 -38
  92. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  93. package/reports/pipeline/discover_github.json +0 -25
  94. package/reports/pipeline/discover_github_repos.json +0 -49
  95. package/reports/pipeline/discover_swaggerhub.json +0 -24
  96. package/reports/pipeline/discover_well_known.json +0 -23
  97. package/reports/pipeline/fetch_specs.json +0 -19
  98. package/reports/pipeline/generate_providers.json +0 -14
  99. package/reports/pipeline/match_registry.json +0 -11
  100. package/reports/pipeline/parse_specs.json +0 -17
  101. package/reports/pipeline/promote_candidates.json +0 -34
  102. package/reports/pipeline/validate.json +0 -30
  103. package/reports/pipeline/validate_smoke_details.json +0 -3835
  104. package/reports/session-report-2026-04-05.html +0 -433
  105. package/seed-apis-direct.mjs +0 -106
  106. package/src/access-control.ts +0 -174
  107. package/src/adapters/base.ts +0 -364
  108. package/src/adapters/claude-desktop.ts +0 -41
  109. package/src/adapters/cline.ts +0 -88
  110. package/src/adapters/continue.ts +0 -91
  111. package/src/adapters/cursor.ts +0 -43
  112. package/src/adapters/custom.ts +0 -188
  113. package/src/adapters/detect.ts +0 -202
  114. package/src/adapters/index.ts +0 -47
  115. package/src/adapters/windsurf.ts +0 -44
  116. package/src/bin-http.ts +0 -45
  117. package/src/bin.ts +0 -34
  118. package/src/capability-router.ts +0 -331
  119. package/src/chainExecutor.ts +0 -730
  120. package/src/chainResolver.test.ts +0 -246
  121. package/src/chainResolver.ts +0 -658
  122. package/src/cli/commands/demo.ts +0 -109
  123. package/src/cli/commands/doctor.ts +0 -435
  124. package/src/cli/commands/index.ts +0 -9
  125. package/src/cli/commands/login.ts +0 -203
  126. package/src/cli/commands/mcp-install.ts +0 -373
  127. package/src/cli/commands/restore.ts +0 -333
  128. package/src/cli/commands/setup.ts +0 -297
  129. package/src/cli/commands/uninstall.ts +0 -240
  130. package/src/cli/index.ts +0 -148
  131. package/src/cli.ts +0 -370
  132. package/src/confirmation.ts +0 -296
  133. package/src/credentials.ts +0 -455
  134. package/src/credits.ts +0 -329
  135. package/src/crypto.ts +0 -75
  136. package/src/discovery.ts +0 -568
  137. package/src/enterprise/env.ts +0 -156
  138. package/src/enterprise/index.ts +0 -7
  139. package/src/enterprise/script-generator.ts +0 -481
  140. package/src/execute-dynamic.ts +0 -617
  141. package/src/execute.ts +0 -2386
  142. package/src/gateway-client.ts +0 -192
  143. package/src/hivr-whitelist.ts +0 -110
  144. package/src/http-api.ts +0 -286
  145. package/src/http-server-minimal.ts +0 -154
  146. package/src/index.ts +0 -2611
  147. package/src/intelligent-gateway.ts +0 -339
  148. package/src/mcp-analytics.ts +0 -156
  149. package/src/metered.ts +0 -149
  150. package/src/open-apis-generated.ts +0 -157
  151. package/src/open-apis.ts +0 -558
  152. package/src/postinstall.ts +0 -18
  153. package/src/product-whitelist.ts +0 -246
  154. package/src/proxy.ts +0 -36
  155. package/src/session.ts +0 -129
  156. package/src/stripe.ts +0 -497
  157. package/src/telemetry.ts +0 -71
  158. package/src/test.ts +0 -135
  159. package/src/types/convex-api.d.ts +0 -20
  160. package/src/types/convex-api.ts +0 -21
  161. package/src/types.ts +0 -109
  162. package/src/ui/colors.ts +0 -219
  163. package/src/ui/errors.ts +0 -394
  164. package/src/ui/index.ts +0 -17
  165. package/src/ui/prompts.ts +0 -390
  166. package/src/ui/spinner.ts +0 -325
  167. package/src/utils/backup.ts +0 -224
  168. package/src/utils/config.ts +0 -318
  169. package/src/utils/os.ts +0 -124
  170. package/src/utils/paths.ts +0 -203
  171. package/src/webhook.ts +0 -107
  172. package/test-10-working.cjs +0 -97
  173. package/test-14-final.cjs +0 -96
  174. package/test-actual-handlers.ts +0 -92
  175. package/test-apilayer-all-14.ts +0 -249
  176. package/test-apilayer-fixed.ts +0 -248
  177. package/test-direct-endpoints.ts +0 -174
  178. package/test-exact-endpoints.ts +0 -144
  179. package/test-final.ts +0 -83
  180. package/test-full-routing.ts +0 -100
  181. package/test-handlers-correct.ts +0 -217
  182. package/test-numverify-key.ts +0 -41
  183. package/test-via-handlers.ts +0 -92
  184. package/test-worldnews.mjs +0 -26
  185. package/tsconfig.json +0 -20
@@ -1,494 +0,0 @@
1
- import { v } from "convex/values";
2
- import { mutation, query, action, internalAction, internalQuery, internalMutation } from "./_generated/server";
3
- import { internal } from "./_generated/api";
4
- import { Doc, Id } from "./_generated/dataModel";
5
-
6
- // Event types available for webhooks
7
- export const WEBHOOK_EVENTS = [
8
- "usage.threshold.80",
9
- "usage.threshold.100",
10
- "api.error",
11
- "agent.connected",
12
- "agent.revoked",
13
- ] as const;
14
-
15
- // Generate a random secret for webhook signature verification
16
- function generateSecret(): string {
17
- const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
18
- let result = "whsec_";
19
- for (let i = 0; i < 32; i++) {
20
- result += chars.charAt(Math.floor(Math.random() * chars.length));
21
- }
22
- return result;
23
- }
24
-
25
- // ============================================
26
- // QUERIES
27
- // ============================================
28
-
29
- export const getWebhooks = query({
30
- args: { token: v.string() },
31
- handler: async (ctx, args) => {
32
- // Verify session
33
- const session = await ctx.db
34
- .query("agentSessions")
35
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
36
- .first();
37
-
38
- if (!session) {
39
- return { error: "Invalid session" };
40
- }
41
-
42
- // Get webhooks for workspace
43
- const webhooks = await ctx.db
44
- .query("webhooks")
45
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
46
- .collect();
47
-
48
- // Return webhooks without exposing full secret
49
- return {
50
- webhooks: webhooks.map((wh) => ({
51
- id: wh._id,
52
- url: wh.url,
53
- events: wh.events,
54
- enabled: wh.enabled,
55
- lastTriggeredAt: wh.lastTriggeredAt,
56
- lastStatus: wh.lastStatus,
57
- failCount: wh.failCount,
58
- createdAt: wh.createdAt,
59
- // Only show hint of secret
60
- secretHint: wh.secret.slice(0, 10) + "..." + wh.secret.slice(-4),
61
- })),
62
- };
63
- },
64
- });
65
-
66
- // ============================================
67
- // MUTATIONS
68
- // ============================================
69
-
70
- export const createWebhook = mutation({
71
- args: {
72
- token: v.string(),
73
- url: v.string(),
74
- events: v.array(v.string()),
75
- },
76
- handler: async (ctx, args) => {
77
- // Verify session
78
- const session = await ctx.db
79
- .query("agentSessions")
80
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
81
- .first();
82
-
83
- if (!session) {
84
- return { error: "Invalid session" };
85
- }
86
-
87
- // Validate URL
88
- try {
89
- new URL(args.url);
90
- } catch {
91
- return { error: "Invalid URL format" };
92
- }
93
-
94
- // Validate URL is HTTPS
95
- if (!args.url.startsWith("https://")) {
96
- return { error: "Webhook URL must use HTTPS" };
97
- }
98
-
99
- // Validate events
100
- const validEvents = args.events.filter((e) =>
101
- WEBHOOK_EVENTS.includes(e as typeof WEBHOOK_EVENTS[number])
102
- );
103
-
104
- if (validEvents.length === 0) {
105
- return { error: "At least one valid event is required" };
106
- }
107
-
108
- // Check webhook limit (max 5 per workspace)
109
- const existingWebhooks = await ctx.db
110
- .query("webhooks")
111
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", session.workspaceId))
112
- .collect();
113
-
114
- if (existingWebhooks.length >= 5) {
115
- return { error: "Maximum 5 webhooks per workspace" };
116
- }
117
-
118
- // Check for duplicate URL
119
- const duplicate = existingWebhooks.find((wh) => wh.url === args.url);
120
- if (duplicate) {
121
- return { error: "A webhook with this URL already exists" };
122
- }
123
-
124
- // Generate secret
125
- const secret = generateSecret();
126
-
127
- // Create webhook
128
- const webhookId = await ctx.db.insert("webhooks", {
129
- workspaceId: session.workspaceId,
130
- url: args.url,
131
- events: validEvents,
132
- secret,
133
- enabled: true,
134
- failCount: 0,
135
- createdAt: Date.now(),
136
- });
137
-
138
- return {
139
- success: true,
140
- webhookId,
141
- secret, // Return secret only once on creation
142
- };
143
- },
144
- });
145
-
146
- export const updateWebhook = mutation({
147
- args: {
148
- token: v.string(),
149
- webhookId: v.id("webhooks"),
150
- enabled: v.optional(v.boolean()),
151
- events: v.optional(v.array(v.string())),
152
- },
153
- handler: async (ctx, args) => {
154
- // Verify session
155
- const session = await ctx.db
156
- .query("agentSessions")
157
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
158
- .first();
159
-
160
- if (!session) {
161
- return { error: "Invalid session" };
162
- }
163
-
164
- // Get webhook
165
- const webhook = await ctx.db.get(args.webhookId);
166
-
167
- if (!webhook || webhook.workspaceId !== session.workspaceId) {
168
- return { error: "Webhook not found" };
169
- }
170
-
171
- // Build update object
172
- const updates: Partial<{
173
- enabled: boolean;
174
- events: string[];
175
- }> = {};
176
-
177
- if (args.enabled !== undefined) {
178
- updates.enabled = args.enabled;
179
- }
180
-
181
- if (args.events !== undefined) {
182
- const validEvents = args.events.filter((e) =>
183
- WEBHOOK_EVENTS.includes(e as typeof WEBHOOK_EVENTS[number])
184
- );
185
- if (validEvents.length === 0) {
186
- return { error: "At least one valid event is required" };
187
- }
188
- updates.events = validEvents;
189
- }
190
-
191
- // Update webhook
192
- await ctx.db.patch(args.webhookId, updates);
193
-
194
- return { success: true };
195
- },
196
- });
197
-
198
- export const deleteWebhook = mutation({
199
- args: {
200
- token: v.string(),
201
- webhookId: v.id("webhooks"),
202
- },
203
- handler: async (ctx, args) => {
204
- // Verify session
205
- const session = await ctx.db
206
- .query("agentSessions")
207
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
208
- .first();
209
-
210
- if (!session) {
211
- return { error: "Invalid session" };
212
- }
213
-
214
- // Get webhook
215
- const webhook = await ctx.db.get(args.webhookId);
216
-
217
- if (!webhook || webhook.workspaceId !== session.workspaceId) {
218
- return { error: "Webhook not found" };
219
- }
220
-
221
- // Delete webhook
222
- await ctx.db.delete(args.webhookId);
223
-
224
- return { success: true };
225
- },
226
- });
227
-
228
- export const regenerateSecret = mutation({
229
- args: {
230
- token: v.string(),
231
- webhookId: v.id("webhooks"),
232
- },
233
- handler: async (ctx, args) => {
234
- // Verify session
235
- const session = await ctx.db
236
- .query("agentSessions")
237
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
238
- .first();
239
-
240
- if (!session) {
241
- return { error: "Invalid session" };
242
- }
243
-
244
- // Get webhook
245
- const webhook = await ctx.db.get(args.webhookId);
246
-
247
- if (!webhook || webhook.workspaceId !== session.workspaceId) {
248
- return { error: "Webhook not found" };
249
- }
250
-
251
- // Generate new secret
252
- const newSecret = generateSecret();
253
-
254
- // Update webhook
255
- await ctx.db.patch(args.webhookId, { secret: newSecret });
256
-
257
- return {
258
- success: true,
259
- secret: newSecret, // Return new secret
260
- };
261
- },
262
- });
263
-
264
- // ============================================
265
- // ACTIONS (for HTTP calls)
266
- // ============================================
267
-
268
- export const testWebhook = action({
269
- args: {
270
- token: v.string(),
271
- webhookId: v.id("webhooks"),
272
- },
273
- returns: v.union(
274
- v.object({ error: v.string() }),
275
- v.object({ success: v.literal(true), status: v.number(), message: v.string() }),
276
- v.object({ success: v.literal(false), status: v.optional(v.number()), message: v.string() })
277
- ),
278
- handler: async (ctx, args): Promise<
279
- | { error: string }
280
- | { success: true; status: number; message: string }
281
- | { success: false; status?: number; message: string }
282
- > => {
283
- // Get webhook from database
284
- const queryResult = await ctx.runQuery(internal.webhooks.getWebhookInternal, {
285
- token: args.token,
286
- webhookId: args.webhookId,
287
- });
288
-
289
- if (!queryResult || "error" in queryResult) {
290
- return { error: queryResult?.error || "Webhook not found" };
291
- }
292
-
293
- const webhook = queryResult.webhook;
294
-
295
- // Create test payload
296
- const payload = {
297
- event: "test",
298
- workspace: webhook.workspaceId,
299
- timestamp: new Date().toISOString(),
300
- data: {
301
- message: "This is a test webhook from APIClaw",
302
- webhookId: args.webhookId,
303
- },
304
- };
305
-
306
- // Sign the payload
307
- const signature = await signPayload(JSON.stringify(payload), webhook.secret);
308
-
309
- try {
310
- const response = await fetch(webhook.url, {
311
- method: "POST",
312
- headers: {
313
- "Content-Type": "application/json",
314
- "X-APIClaw-Signature": signature,
315
- "X-APIClaw-Event": "test",
316
- "X-APIClaw-Timestamp": payload.timestamp,
317
- },
318
- body: JSON.stringify(payload),
319
- });
320
-
321
- if (response.ok) {
322
- return {
323
- success: true as const,
324
- status: response.status,
325
- message: "Webhook delivered successfully",
326
- };
327
- } else {
328
- return {
329
- success: false as const,
330
- status: response.status,
331
- message: `Webhook returned status ${response.status}`,
332
- };
333
- }
334
- } catch (error) {
335
- return {
336
- success: false as const,
337
- message: error instanceof Error ? error.message : "Failed to deliver webhook",
338
- };
339
- }
340
- },
341
- });
342
-
343
- // Internal action to trigger webhooks (called from other parts of the system)
344
- export const triggerWebhooks = internalAction({
345
- args: {
346
- workspaceId: v.id("workspaces"),
347
- event: v.string(),
348
- data: v.any(),
349
- },
350
- returns: v.object({ triggered: v.number(), total: v.optional(v.number()) }),
351
- handler: async (ctx, args): Promise<{ triggered: number; total?: number }> => {
352
- // Get all enabled webhooks for this workspace that subscribe to this event
353
- const webhooksResult = await ctx.runQuery(internal.webhooks.getWebhooksForEvent, {
354
- workspaceId: args.workspaceId,
355
- event: args.event,
356
- });
357
-
358
- if (!webhooksResult || webhooksResult.length === 0) {
359
- return { triggered: 0 };
360
- }
361
-
362
- const payload = {
363
- event: args.event,
364
- workspace: args.workspaceId,
365
- timestamp: new Date().toISOString(),
366
- data: args.data,
367
- };
368
-
369
- const payloadString = JSON.stringify(payload);
370
- let successCount = 0;
371
-
372
- // Send to each webhook
373
- for (const webhook of webhooksResult) {
374
- const signature = await signPayload(payloadString, webhook.secret);
375
-
376
- try {
377
- const response = await fetch(webhook.url, {
378
- method: "POST",
379
- headers: {
380
- "Content-Type": "application/json",
381
- "X-APIClaw-Signature": signature,
382
- "X-APIClaw-Event": args.event,
383
- "X-APIClaw-Timestamp": payload.timestamp,
384
- },
385
- body: payloadString,
386
- });
387
-
388
- // Update webhook status
389
- await ctx.runMutation(internal.webhooks.updateWebhookStatus, {
390
- webhookId: webhook._id,
391
- success: response.ok,
392
- });
393
-
394
- if (response.ok) {
395
- successCount++;
396
- }
397
- } catch {
398
- // Update webhook with failure
399
- await ctx.runMutation(internal.webhooks.updateWebhookStatus, {
400
- webhookId: webhook._id,
401
- success: false,
402
- });
403
- }
404
- }
405
-
406
- return { triggered: successCount, total: webhooksResult.length };
407
- },
408
- });
409
-
410
- // ============================================
411
- // INTERNAL QUERIES/MUTATIONS (for actions)
412
- // ============================================
413
-
414
- export const getWebhookInternal = internalQuery({
415
- args: {
416
- token: v.string(),
417
- webhookId: v.id("webhooks"),
418
- },
419
- handler: async (ctx, args): Promise<{ error: string } | { webhook: Doc<"webhooks"> }> => {
420
- // Verify session
421
- const session = await ctx.db
422
- .query("agentSessions")
423
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
424
- .first();
425
-
426
- if (!session) {
427
- return { error: "Invalid session" };
428
- }
429
-
430
- // Get webhook
431
- const webhook = await ctx.db.get(args.webhookId);
432
-
433
- if (!webhook || webhook.workspaceId !== session.workspaceId) {
434
- return { error: "Webhook not found" };
435
- }
436
-
437
- return { webhook };
438
- },
439
- });
440
-
441
- export const getWebhooksForEvent = internalQuery({
442
- args: {
443
- workspaceId: v.id("workspaces"),
444
- event: v.string(),
445
- },
446
- handler: async (ctx, args): Promise<Doc<"webhooks">[]> => {
447
- const webhooks = await ctx.db
448
- .query("webhooks")
449
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", args.workspaceId))
450
- .collect();
451
-
452
- // Filter for enabled webhooks that subscribe to this event
453
- return webhooks.filter(
454
- (wh) => wh.enabled && wh.events.includes(args.event)
455
- );
456
- },
457
- });
458
-
459
- export const updateWebhookStatus = internalMutation({
460
- args: {
461
- webhookId: v.id("webhooks"),
462
- success: v.boolean(),
463
- },
464
- handler: async (ctx, args) => {
465
- const webhook = await ctx.db.get(args.webhookId);
466
- if (!webhook) return;
467
-
468
- await ctx.db.patch(args.webhookId, {
469
- lastTriggeredAt: Date.now(),
470
- lastStatus: args.success ? "success" : "failed",
471
- failCount: args.success ? 0 : webhook.failCount + 1,
472
- });
473
-
474
- // Disable webhook after 5 consecutive failures
475
- if (!args.success && webhook.failCount + 1 >= 5) {
476
- await ctx.db.patch(args.webhookId, { enabled: false });
477
- }
478
- },
479
- });
480
-
481
- // ============================================
482
- // HELPERS
483
- // ============================================
484
-
485
- async function signPayload(payload: string, secret: string): Promise<string> {
486
- // Simple HMAC-like signature using SHA-256
487
- // In a production environment, use proper crypto
488
- const encoder = new TextEncoder();
489
- const data = encoder.encode(payload + secret);
490
- const hashBuffer = await crypto.subtle.digest("SHA-256", data);
491
- const hashArray = Array.from(new Uint8Array(hashBuffer));
492
- const hashHex = hashArray.map((b) => b.toString(16).padStart(2, "0")).join("");
493
- return `sha256=${hashHex}`;
494
- }
@@ -1,143 +0,0 @@
1
- import { mutation, query, internalQuery } from "./_generated/server";
2
- import { v } from "convex/values";
3
-
4
- // ============================================
5
- // QUERIES
6
- // ============================================
7
-
8
- /** Get workspace settings (returns defaults if none saved) */
9
- export const get = query({
10
- args: { workspaceId: v.id("workspaces") },
11
- handler: async (ctx, { workspaceId }) => {
12
- const settings = await ctx.db
13
- .query("workspaceSettings")
14
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
15
- .first();
16
-
17
- if (!settings) {
18
- return {
19
- workspaceId,
20
- routingMode: "balanced",
21
- defaultModel: null,
22
- maxPricePerMTokens: null,
23
- monthlyBudgetLimit: null,
24
- preferredProviders: [],
25
- blockedProviders: [],
26
- allowOpenRouterFallback: true,
27
- _isDefault: true,
28
- };
29
- }
30
-
31
- return { ...settings, _isDefault: false };
32
- },
33
- });
34
-
35
- /** Internal: get settings for routing (called from http actions) */
36
- export const getForRouting = internalQuery({
37
- args: { workspaceId: v.string() },
38
- handler: async (ctx, { workspaceId }) => {
39
- // Try to find settings - workspaceId comes as string from http handlers
40
- const all = await ctx.db.query("workspaceSettings").collect();
41
- const settings = all.find((s) => String(s.workspaceId) === workspaceId);
42
-
43
- // Get workspace tier for premium features (OAuth passthrough etc.)
44
- const allWorkspaces = await ctx.db.query("workspaces").collect();
45
- const workspace = allWorkspaces.find((w) => String(w._id) === workspaceId);
46
- const tier = workspace?.tier ?? "free";
47
-
48
- if (!settings) {
49
- return {
50
- routingMode: "balanced" as const,
51
- defaultModel: null as string | null,
52
- maxPricePerMTokens: null as number | null,
53
- monthlyBudgetLimit: null as number | null,
54
- preferredProviders: [] as string[],
55
- blockedProviders: [] as string[],
56
- allowOpenRouterFallback: true,
57
- tier,
58
- };
59
- }
60
-
61
- return {
62
- routingMode: settings.routingMode,
63
- defaultModel: settings.defaultModel ?? null,
64
- maxPricePerMTokens: settings.maxPricePerMTokens ?? null,
65
- monthlyBudgetLimit: settings.monthlyBudgetLimit ?? null,
66
- preferredProviders: settings.preferredProviders ?? [],
67
- blockedProviders: settings.blockedProviders ?? [],
68
- allowOpenRouterFallback: settings.allowOpenRouterFallback ?? true,
69
- tier,
70
- };
71
- },
72
- });
73
-
74
- // ============================================
75
- // MUTATIONS
76
- // ============================================
77
-
78
- /** Create or update workspace settings */
79
- export const upsert = mutation({
80
- args: {
81
- token: v.string(),
82
- routingMode: v.optional(v.string()),
83
- defaultModel: v.optional(v.string()),
84
- maxPricePerMTokens: v.optional(v.float64()),
85
- monthlyBudgetLimit: v.optional(v.float64()),
86
- preferredProviders: v.optional(v.array(v.string())),
87
- blockedProviders: v.optional(v.array(v.string())),
88
- allowOpenRouterFallback: v.optional(v.boolean()),
89
- },
90
- handler: async (ctx, args) => {
91
- // Resolve workspace from session token
92
- const session = await ctx.db
93
- .query("agentSessions")
94
- .withIndex("by_sessionToken", (q) => q.eq("sessionToken", args.token))
95
- .first();
96
-
97
- if (!session) {
98
- throw new Error("Invalid or expired session");
99
- }
100
-
101
- const workspaceId = session.workspaceId;
102
- const now = Date.now();
103
-
104
- // Validate routingMode
105
- const validModes = ["best_price", "highest_quality", "fastest", "balanced"];
106
- if (args.routingMode && !validModes.includes(args.routingMode)) {
107
- throw new Error(`Invalid routingMode. Must be one of: ${validModes.join(", ")}`);
108
- }
109
-
110
- // Check if settings exist
111
- const existing = await ctx.db
112
- .query("workspaceSettings")
113
- .withIndex("by_workspaceId", (q) => q.eq("workspaceId", workspaceId))
114
- .first();
115
-
116
- const updates: Record<string, unknown> = { updatedAt: now };
117
- if (args.routingMode !== undefined) updates.routingMode = args.routingMode;
118
- if (args.defaultModel !== undefined) updates.defaultModel = args.defaultModel;
119
- if (args.maxPricePerMTokens !== undefined) updates.maxPricePerMTokens = args.maxPricePerMTokens;
120
- if (args.monthlyBudgetLimit !== undefined) updates.monthlyBudgetLimit = args.monthlyBudgetLimit;
121
- if (args.preferredProviders !== undefined) updates.preferredProviders = args.preferredProviders;
122
- if (args.blockedProviders !== undefined) updates.blockedProviders = args.blockedProviders;
123
- if (args.allowOpenRouterFallback !== undefined) updates.allowOpenRouterFallback = args.allowOpenRouterFallback;
124
-
125
- if (existing) {
126
- await ctx.db.patch(existing._id, updates);
127
- return existing._id;
128
- }
129
-
130
- return await ctx.db.insert("workspaceSettings", {
131
- workspaceId,
132
- routingMode: args.routingMode || "balanced",
133
- defaultModel: args.defaultModel,
134
- maxPricePerMTokens: args.maxPricePerMTokens,
135
- monthlyBudgetLimit: args.monthlyBudgetLimit,
136
- preferredProviders: args.preferredProviders,
137
- blockedProviders: args.blockedProviders,
138
- allowOpenRouterFallback: args.allowOpenRouterFallback ?? true,
139
- createdAt: now,
140
- updatedAt: now,
141
- });
142
- },
143
- });