@nordsym/apiclaw 1.2.2 → 1.2.3
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/AGENTS.md +50 -33
- package/README.md +22 -12
- package/SOUL.md +60 -19
- package/STATUS.md +91 -169
- package/convex/_generated/api.d.ts +6 -0
- package/convex/directCall.ts +598 -0
- package/convex/providers.ts +341 -26
- package/convex/schema.ts +87 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.ts +55 -0
- package/data/combined-02-26.json +22102 -0
- package/data/night-expansion-02-26-06-batch2.json +1898 -0
- package/data/night-expansion-02-26-06-batch3.json +1410 -0
- package/data/night-expansion-02-26-06.json +3146 -0
- package/data/night-expansion-02-26-full.json +9726 -0
- package/data/night-expansion-02-26-v2.json +330 -0
- package/data/night-expansion-02-26.json +171 -0
- package/dist/crypto.d.ts +7 -0
- package/dist/crypto.d.ts.map +1 -0
- package/dist/crypto.js +67 -0
- package/dist/crypto.js.map +1 -0
- package/dist/execute-dynamic.d.ts +116 -0
- package/dist/execute-dynamic.d.ts.map +1 -0
- package/dist/execute-dynamic.js +456 -0
- package/dist/execute-dynamic.js.map +1 -0
- package/dist/execute.d.ts +2 -1
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +35 -5
- package/dist/execute.js.map +1 -1
- package/dist/index.js +33 -4
- package/dist/index.js.map +1 -1
- package/dist/registry/apis.json +2081 -3
- package/docs/PRD-customer-key-passthrough.md +184 -0
- package/landing/public/badges/available-on-apiclaw.svg +14 -0
- package/landing/scripts/generate-stats.js +75 -4
- package/landing/src/app/admin/page.tsx +1 -1
- package/landing/src/app/api/auth/magic-link/route.ts +1 -1
- package/landing/src/app/api/auth/session/route.ts +1 -1
- package/landing/src/app/api/auth/verify/route.ts +1 -1
- package/landing/src/app/api/og/route.tsx +5 -3
- package/landing/src/app/docs/page.tsx +5 -4
- package/landing/src/app/earn/page.tsx +14 -11
- package/landing/src/app/globals.css +16 -15
- package/landing/src/app/layout.tsx +2 -2
- package/landing/src/app/page.tsx +425 -254
- package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +600 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +583 -0
- package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +301 -0
- package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +659 -0
- package/landing/src/app/providers/dashboard/[apiId]/page.tsx +381 -0
- package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +418 -0
- package/landing/src/app/providers/dashboard/layout.tsx +292 -0
- package/landing/src/app/providers/dashboard/page.tsx +353 -290
- package/landing/src/app/providers/register/page.tsx +87 -10
- package/landing/src/components/AiClientDropdown.tsx +85 -0
- package/landing/src/components/ConfigHelperModal.tsx +113 -0
- package/landing/src/components/HeroTabs.tsx +187 -0
- package/landing/src/components/ShareIntegrationModal.tsx +198 -0
- package/landing/src/hooks/useDashboardData.ts +53 -1
- package/landing/src/lib/apis.json +46554 -174
- package/landing/src/lib/convex-client.ts +22 -3
- package/landing/src/lib/stats.json +4 -4
- package/landing/tsconfig.tsbuildinfo +1 -1
- package/night-expansion-02-26-06-batch2.py +368 -0
- package/night-expansion-02-26-06-batch3.py +299 -0
- package/night-expansion-02-26-06.py +756 -0
- package/package.json +1 -1
- package/scripts/bulk-add-public-apis-v2.py +418 -0
- package/scripts/night-expansion-02-26-v2.py +296 -0
- package/scripts/night-expansion-02-26.py +890 -0
- package/scripts/seed-complete-api.js +181 -0
- package/scripts/seed-demo-api.sh +44 -0
- package/src/crypto.ts +75 -0
- package/src/execute-dynamic.ts +589 -0
- package/src/execute.ts +41 -5
- package/src/index.ts +38 -4
- package/src/registry/apis.json +2081 -3
package/convex/providers.ts
CHANGED
|
@@ -62,7 +62,18 @@ export const registerProvider = mutation({
|
|
|
62
62
|
discoveryCount: 0,
|
|
63
63
|
});
|
|
64
64
|
|
|
65
|
-
|
|
65
|
+
// Create session for auto-login after registration
|
|
66
|
+
const sessionToken = generateToken();
|
|
67
|
+
const sessionExpiresAt = now + 30 * 24 * 60 * 60 * 1000; // 30 days
|
|
68
|
+
|
|
69
|
+
await ctx.db.insert("sessions", {
|
|
70
|
+
providerId,
|
|
71
|
+
token: sessionToken,
|
|
72
|
+
expiresAt: sessionExpiresAt,
|
|
73
|
+
createdAt: now,
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return { providerId, apiId, sessionToken };
|
|
66
77
|
},
|
|
67
78
|
});
|
|
68
79
|
|
|
@@ -314,6 +325,238 @@ export const getSession = query({
|
|
|
314
325
|
// DASHBOARD ANALYTICS
|
|
315
326
|
// ============================================
|
|
316
327
|
|
|
328
|
+
// Get single API by ID
|
|
329
|
+
export const getApiById = query({
|
|
330
|
+
args: { apiId: v.string() },
|
|
331
|
+
handler: async (ctx, args) => {
|
|
332
|
+
// Try to get by document ID
|
|
333
|
+
try {
|
|
334
|
+
const api = await ctx.db.get(args.apiId as any);
|
|
335
|
+
if (api) {
|
|
336
|
+
// Check if it has Direct Call configured
|
|
337
|
+
const directCall = await ctx.db
|
|
338
|
+
.query("providerDirectCall")
|
|
339
|
+
.filter((q) => q.eq(q.field("apiId"), args.apiId))
|
|
340
|
+
.first();
|
|
341
|
+
return { ...api, hasDirectCall: !!directCall, directCallStatus: directCall?.status };
|
|
342
|
+
}
|
|
343
|
+
} catch {
|
|
344
|
+
// Not a valid ID format
|
|
345
|
+
}
|
|
346
|
+
return null;
|
|
347
|
+
},
|
|
348
|
+
});
|
|
349
|
+
|
|
350
|
+
// Get provider APIs with Direct Call status
|
|
351
|
+
export const getProviderAPIsWithStatus = query({
|
|
352
|
+
args: { providerId: v.string() },
|
|
353
|
+
handler: async (ctx, args) => {
|
|
354
|
+
const apis = await ctx.db
|
|
355
|
+
.query("providerAPIs")
|
|
356
|
+
.filter((q) => q.eq(q.field("providerId"), args.providerId as any))
|
|
357
|
+
.collect();
|
|
358
|
+
|
|
359
|
+
// Add Direct Call status to each API
|
|
360
|
+
const apisWithStatus = await Promise.all(
|
|
361
|
+
apis.map(async (api) => {
|
|
362
|
+
const directCall = await ctx.db
|
|
363
|
+
.query("providerDirectCall")
|
|
364
|
+
.filter((q) => q.eq(q.field("apiId"), api._id))
|
|
365
|
+
.first();
|
|
366
|
+
return {
|
|
367
|
+
...api,
|
|
368
|
+
hasDirectCall: !!directCall,
|
|
369
|
+
directCallStatus: directCall?.status,
|
|
370
|
+
};
|
|
371
|
+
})
|
|
372
|
+
);
|
|
373
|
+
|
|
374
|
+
return apisWithStatus;
|
|
375
|
+
},
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
// DEBUG: Delete API
|
|
379
|
+
export const debugDeleteAPI = mutation({
|
|
380
|
+
args: { apiId: v.string() },
|
|
381
|
+
handler: async (ctx, args) => {
|
|
382
|
+
await ctx.db.delete(args.apiId as any);
|
|
383
|
+
return { deleted: true };
|
|
384
|
+
},
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Add API for logged-in provider (used by register page)
|
|
388
|
+
export const addAPI = mutation({
|
|
389
|
+
args: {
|
|
390
|
+
token: v.string(),
|
|
391
|
+
api: v.object({
|
|
392
|
+
name: v.string(),
|
|
393
|
+
description: v.string(),
|
|
394
|
+
category: v.string(),
|
|
395
|
+
openApiUrl: v.optional(v.string()),
|
|
396
|
+
docsUrl: v.optional(v.string()),
|
|
397
|
+
pricingModel: v.string(),
|
|
398
|
+
pricingNotes: v.optional(v.string()),
|
|
399
|
+
}),
|
|
400
|
+
},
|
|
401
|
+
handler: async (ctx, args) => {
|
|
402
|
+
// Verify session
|
|
403
|
+
const session = await ctx.db
|
|
404
|
+
.query("sessions")
|
|
405
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
406
|
+
.first();
|
|
407
|
+
|
|
408
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
409
|
+
throw new Error("Invalid or expired session");
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
const now = Date.now();
|
|
413
|
+
const apiId = await ctx.db.insert("providerAPIs", {
|
|
414
|
+
providerId: session.providerId,
|
|
415
|
+
name: args.api.name,
|
|
416
|
+
description: args.api.description,
|
|
417
|
+
category: args.api.category,
|
|
418
|
+
openApiUrl: args.api.openApiUrl,
|
|
419
|
+
docsUrl: args.api.docsUrl,
|
|
420
|
+
pricingModel: args.api.pricingModel,
|
|
421
|
+
pricingNotes: args.api.pricingNotes,
|
|
422
|
+
status: "approved",
|
|
423
|
+
createdAt: now,
|
|
424
|
+
approvedAt: now,
|
|
425
|
+
discoveryCount: 0,
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
return { apiId, success: true };
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
|
|
432
|
+
// Delete API for logged-in provider
|
|
433
|
+
export const deleteAPI = mutation({
|
|
434
|
+
args: {
|
|
435
|
+
token: v.string(),
|
|
436
|
+
apiId: v.string(),
|
|
437
|
+
},
|
|
438
|
+
handler: async (ctx, args) => {
|
|
439
|
+
// Verify session
|
|
440
|
+
const session = await ctx.db
|
|
441
|
+
.query("sessions")
|
|
442
|
+
.withIndex("by_token", (q) => q.eq("token", args.token))
|
|
443
|
+
.first();
|
|
444
|
+
|
|
445
|
+
if (!session || session.expiresAt < Date.now()) {
|
|
446
|
+
throw new Error("Invalid or expired session");
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
// Get the API and verify ownership
|
|
450
|
+
const api = await ctx.db.get(args.apiId as any);
|
|
451
|
+
if (!api || (api as any).providerId !== session.providerId) {
|
|
452
|
+
throw new Error("API not found or unauthorized");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
// Delete the API
|
|
456
|
+
await ctx.db.delete(args.apiId as any);
|
|
457
|
+
|
|
458
|
+
// Also delete any Direct Call config
|
|
459
|
+
const directCallConfig = await ctx.db
|
|
460
|
+
.query("providerDirectCall")
|
|
461
|
+
.filter((q) => q.eq(q.field("apiId"), args.apiId))
|
|
462
|
+
.first();
|
|
463
|
+
if (directCallConfig) {
|
|
464
|
+
await ctx.db.delete(directCallConfig._id);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
return { deleted: true };
|
|
468
|
+
},
|
|
469
|
+
});
|
|
470
|
+
|
|
471
|
+
// DEBUG: Update provider name
|
|
472
|
+
export const debugUpdateProvider = mutation({
|
|
473
|
+
args: {
|
|
474
|
+
providerId: v.string(),
|
|
475
|
+
name: v.optional(v.string()),
|
|
476
|
+
},
|
|
477
|
+
handler: async (ctx, args) => {
|
|
478
|
+
const updates: any = {};
|
|
479
|
+
if (args.name) updates.name = args.name;
|
|
480
|
+
await ctx.db.patch(args.providerId as any, updates);
|
|
481
|
+
return { updated: true };
|
|
482
|
+
},
|
|
483
|
+
});
|
|
484
|
+
|
|
485
|
+
// DEBUG: Add API for provider (seeding)
|
|
486
|
+
export const debugAddAPI = mutation({
|
|
487
|
+
args: {
|
|
488
|
+
providerId: v.string(),
|
|
489
|
+
name: v.string(),
|
|
490
|
+
description: v.string(),
|
|
491
|
+
category: v.string(),
|
|
492
|
+
docsUrl: v.optional(v.string()),
|
|
493
|
+
pricingModel: v.string(),
|
|
494
|
+
pricingNotes: v.optional(v.string()),
|
|
495
|
+
},
|
|
496
|
+
handler: async (ctx, args) => {
|
|
497
|
+
const now = Date.now();
|
|
498
|
+
return await ctx.db.insert("providerAPIs", {
|
|
499
|
+
providerId: args.providerId as any,
|
|
500
|
+
name: args.name,
|
|
501
|
+
description: args.description,
|
|
502
|
+
category: args.category,
|
|
503
|
+
docsUrl: args.docsUrl,
|
|
504
|
+
pricingModel: args.pricingModel,
|
|
505
|
+
pricingNotes: args.pricingNotes,
|
|
506
|
+
status: "approved",
|
|
507
|
+
createdAt: now,
|
|
508
|
+
approvedAt: now,
|
|
509
|
+
discoveryCount: 0,
|
|
510
|
+
});
|
|
511
|
+
},
|
|
512
|
+
});
|
|
513
|
+
|
|
514
|
+
// DEBUG: Delete provider and all related data
|
|
515
|
+
export const debugDeleteProvider = mutation({
|
|
516
|
+
args: { providerId: v.string() },
|
|
517
|
+
handler: async (ctx, args) => {
|
|
518
|
+
const providerId = args.providerId as any;
|
|
519
|
+
|
|
520
|
+
// Delete sessions
|
|
521
|
+
const sessions = await ctx.db.query("sessions").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
522
|
+
for (const s of sessions) await ctx.db.delete(s._id);
|
|
523
|
+
|
|
524
|
+
// Delete APIs
|
|
525
|
+
const apis = await ctx.db.query("providerAPIs").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
526
|
+
for (const a of apis) await ctx.db.delete(a._id);
|
|
527
|
+
|
|
528
|
+
// Delete direct call configs
|
|
529
|
+
const configs = await ctx.db.query("providerDirectCall").filter(q => q.eq(q.field("providerId"), providerId)).collect();
|
|
530
|
+
for (const c of configs) {
|
|
531
|
+
// Delete actions for this config
|
|
532
|
+
const actions = await ctx.db.query("providerActions").filter(q => q.eq(q.field("directCallId"), c._id)).collect();
|
|
533
|
+
for (const act of actions) await ctx.db.delete(act._id);
|
|
534
|
+
await ctx.db.delete(c._id);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Delete provider
|
|
538
|
+
await ctx.db.delete(providerId);
|
|
539
|
+
|
|
540
|
+
return { deleted: true };
|
|
541
|
+
},
|
|
542
|
+
});
|
|
543
|
+
|
|
544
|
+
// DEBUG: List all sessions
|
|
545
|
+
export const debugListSessions = query({
|
|
546
|
+
args: {},
|
|
547
|
+
handler: async (ctx) => {
|
|
548
|
+
return await ctx.db.query("sessions").collect();
|
|
549
|
+
},
|
|
550
|
+
});
|
|
551
|
+
|
|
552
|
+
// DEBUG: List all providers
|
|
553
|
+
export const debugListProviders = query({
|
|
554
|
+
args: {},
|
|
555
|
+
handler: async (ctx) => {
|
|
556
|
+
return await ctx.db.query("providers").collect();
|
|
557
|
+
},
|
|
558
|
+
});
|
|
559
|
+
|
|
317
560
|
export const getAnalytics = query({
|
|
318
561
|
args: {
|
|
319
562
|
token: v.string(),
|
|
@@ -338,45 +581,56 @@ export const getAnalytics = query({
|
|
|
338
581
|
|
|
339
582
|
const startTime = now - periodMs;
|
|
340
583
|
|
|
341
|
-
// Get
|
|
342
|
-
const
|
|
343
|
-
.query("
|
|
584
|
+
// Get usage logs for this provider (from Direct Call usageLog)
|
|
585
|
+
const usageLogs = await ctx.db
|
|
586
|
+
.query("usageLog")
|
|
344
587
|
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
345
588
|
.collect();
|
|
346
589
|
|
|
347
|
-
const periodCalls =
|
|
590
|
+
const periodCalls = usageLogs.filter((c) => c.timestamp >= startTime);
|
|
348
591
|
|
|
349
592
|
// Calculate metrics
|
|
350
593
|
const totalCalls = periodCalls.length;
|
|
351
|
-
const uniqueAgents = new Set(periodCalls.map((c) => c.
|
|
352
|
-
const totalRevenue = periodCalls.reduce((sum, c) => sum + c.
|
|
594
|
+
const uniqueAgents = new Set(periodCalls.map((c) => c.userId)).size;
|
|
595
|
+
const totalRevenue = periodCalls.reduce((sum, c) => sum + (c.creditsUsed / 100), 0); // cents to dollars
|
|
596
|
+
const successCount = periodCalls.filter((c) => c.success).length;
|
|
597
|
+
const successRate = totalCalls > 0 ? (successCount / totalCalls) * 100 : 100;
|
|
598
|
+
const avgLatency = totalCalls > 0
|
|
599
|
+
? periodCalls.reduce((sum, c) => sum + c.latencyMs, 0) / totalCalls
|
|
600
|
+
: 0;
|
|
353
601
|
|
|
354
602
|
// Calls over time (daily buckets)
|
|
355
|
-
const callsByDay: Record<string, number> = {};
|
|
356
|
-
const revenueByDay: Record<string, number> = {};
|
|
603
|
+
const callsByDay: Record<string, { calls: number; revenue: number; success: number }> = {};
|
|
357
604
|
|
|
358
605
|
periodCalls.forEach((call) => {
|
|
359
606
|
const day = new Date(call.timestamp).toISOString().split("T")[0];
|
|
360
|
-
|
|
361
|
-
|
|
607
|
+
if (!callsByDay[day]) {
|
|
608
|
+
callsByDay[day] = { calls: 0, revenue: 0, success: 0 };
|
|
609
|
+
}
|
|
610
|
+
callsByDay[day].calls += 1;
|
|
611
|
+
callsByDay[day].revenue += call.creditsUsed / 100;
|
|
612
|
+
if (call.success) callsByDay[day].success += 1;
|
|
362
613
|
});
|
|
363
614
|
|
|
364
|
-
// Top agents
|
|
615
|
+
// Top agents (users)
|
|
365
616
|
const agentCallCounts: Record<string, number> = {};
|
|
366
617
|
periodCalls.forEach((call) => {
|
|
367
|
-
agentCallCounts[call.
|
|
618
|
+
agentCallCounts[call.userId] = (agentCallCounts[call.userId] || 0) + 1;
|
|
368
619
|
});
|
|
369
620
|
const topAgents = Object.entries(agentCallCounts)
|
|
370
621
|
.sort((a, b) => b[1] - a[1])
|
|
371
622
|
.slice(0, 10)
|
|
372
623
|
.map(([agentId, calls]) => ({ agentId, calls }));
|
|
373
624
|
|
|
374
|
-
//
|
|
375
|
-
const
|
|
625
|
+
// Top actions
|
|
626
|
+
const actionCallCounts: Record<string, number> = {};
|
|
376
627
|
periodCalls.forEach((call) => {
|
|
377
|
-
|
|
378
|
-
callsByRegion[region] = (callsByRegion[region] || 0) + 1;
|
|
628
|
+
actionCallCounts[call.actionName] = (actionCallCounts[call.actionName] || 0) + 1;
|
|
379
629
|
});
|
|
630
|
+
const topActions = Object.entries(actionCallCounts)
|
|
631
|
+
.sort((a, b) => b[1] - a[1])
|
|
632
|
+
.slice(0, 10)
|
|
633
|
+
.map(([actionName, calls]) => ({ actionName, calls }));
|
|
380
634
|
|
|
381
635
|
// Get provider's APIs
|
|
382
636
|
const apis = await ctx.db
|
|
@@ -384,32 +638,93 @@ export const getAnalytics = query({
|
|
|
384
638
|
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
385
639
|
.collect();
|
|
386
640
|
|
|
387
|
-
//
|
|
388
|
-
const
|
|
641
|
+
// Get Direct Call configs to map directCallId to apiId
|
|
642
|
+
const directCallConfigs = await ctx.db
|
|
643
|
+
.query("providerDirectCall")
|
|
644
|
+
.withIndex("by_providerId", (q) => q.eq("providerId", session.providerId))
|
|
645
|
+
.collect();
|
|
646
|
+
|
|
647
|
+
// Calls per API (via directCallId → apiId mapping)
|
|
648
|
+
const callsByDirectCallId: Record<string, number> = {};
|
|
389
649
|
periodCalls.forEach((call) => {
|
|
390
|
-
const
|
|
391
|
-
|
|
650
|
+
const dcId = call.directCallId as string;
|
|
651
|
+
callsByDirectCallId[dcId] = (callsByDirectCallId[dcId] || 0) + 1;
|
|
652
|
+
});
|
|
653
|
+
|
|
654
|
+
// Map to apiId
|
|
655
|
+
const callsByApiId: Record<string, number> = {};
|
|
656
|
+
directCallConfigs.forEach((dc) => {
|
|
657
|
+
if (dc.apiId) {
|
|
658
|
+
callsByApiId[dc.apiId as string] = callsByDirectCallId[dc._id as string] || 0;
|
|
659
|
+
}
|
|
392
660
|
});
|
|
393
661
|
|
|
662
|
+
// Preview data for providers with no usage yet
|
|
663
|
+
const isPreview = totalCalls === 0;
|
|
664
|
+
|
|
665
|
+
if (isPreview) {
|
|
666
|
+
// Generate preview data so providers can see what the dashboard looks like
|
|
667
|
+
const previewDays = [];
|
|
668
|
+
for (let i = 13; i >= 0; i--) {
|
|
669
|
+
const date = new Date(now - i * 24 * 60 * 60 * 1000).toISOString().split("T")[0];
|
|
670
|
+
previewDays.push({
|
|
671
|
+
date,
|
|
672
|
+
calls: Math.floor(Math.random() * 50) + 10,
|
|
673
|
+
revenue: Math.random() * 5,
|
|
674
|
+
});
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
return {
|
|
678
|
+
totalCalls: 847,
|
|
679
|
+
uniqueAgents: 23,
|
|
680
|
+
totalRevenue: 42.35,
|
|
681
|
+
successRate: 98.2,
|
|
682
|
+
avgLatency: 145,
|
|
683
|
+
callsByDay: previewDays,
|
|
684
|
+
topAgents: [
|
|
685
|
+
{ agentId: "agent_demo_1", calls: 234 },
|
|
686
|
+
{ agentId: "agent_demo_2", calls: 189 },
|
|
687
|
+
{ agentId: "agent_demo_3", calls: 156 },
|
|
688
|
+
{ agentId: "agent_demo_4", calls: 98 },
|
|
689
|
+
{ agentId: "agent_demo_5", calls: 67 },
|
|
690
|
+
],
|
|
691
|
+
topActions: [
|
|
692
|
+
{ actionName: "send_message", calls: 412 },
|
|
693
|
+
{ actionName: "get_status", calls: 289 },
|
|
694
|
+
{ actionName: "create_invoice", calls: 146 },
|
|
695
|
+
],
|
|
696
|
+
apis: apis.map((api) => ({
|
|
697
|
+
id: api._id,
|
|
698
|
+
name: api.name,
|
|
699
|
+
calls: Math.floor(Math.random() * 200) + 50,
|
|
700
|
+
status: api.status,
|
|
701
|
+
})),
|
|
702
|
+
isPreview: true,
|
|
703
|
+
};
|
|
704
|
+
}
|
|
705
|
+
|
|
394
706
|
return {
|
|
395
707
|
totalCalls,
|
|
396
708
|
uniqueAgents,
|
|
397
709
|
totalRevenue,
|
|
710
|
+
successRate,
|
|
711
|
+
avgLatency,
|
|
398
712
|
callsByDay: Object.entries(callsByDay)
|
|
399
|
-
.map(([date,
|
|
713
|
+
.map(([date, data]) => ({
|
|
400
714
|
date,
|
|
401
|
-
calls,
|
|
402
|
-
revenue:
|
|
715
|
+
calls: data.calls,
|
|
716
|
+
revenue: data.revenue,
|
|
403
717
|
}))
|
|
404
718
|
.sort((a, b) => a.date.localeCompare(b.date)),
|
|
405
719
|
topAgents,
|
|
406
|
-
|
|
720
|
+
topActions,
|
|
407
721
|
apis: apis.map((api) => ({
|
|
408
722
|
id: api._id,
|
|
409
723
|
name: api.name,
|
|
410
|
-
calls:
|
|
724
|
+
calls: callsByApiId[api._id as string] || 0,
|
|
411
725
|
status: api.status,
|
|
412
726
|
})),
|
|
727
|
+
isPreview: false,
|
|
413
728
|
};
|
|
414
729
|
},
|
|
415
730
|
});
|
package/convex/schema.ts
CHANGED
|
@@ -217,4 +217,91 @@ export default defineSchema({
|
|
|
217
217
|
})
|
|
218
218
|
.index("by_type", ["type"])
|
|
219
219
|
.index("by_timestamp", ["timestamp"]),
|
|
220
|
+
|
|
221
|
+
// ============================================
|
|
222
|
+
// SELF-SERVICE DIRECT CALL TABLES
|
|
223
|
+
// ============================================
|
|
224
|
+
|
|
225
|
+
// Provider Direct Call configuration (master key, limits, pricing)
|
|
226
|
+
providerDirectCall: defineTable({
|
|
227
|
+
providerId: v.id("providers"),
|
|
228
|
+
apiId: v.optional(v.id("providerAPIs")),
|
|
229
|
+
baseUrl: v.string(),
|
|
230
|
+
authType: v.string(), // "bearer" | "basic" | "api_key" | "none"
|
|
231
|
+
authHeader: v.string(), // e.g. "Authorization", "X-API-Key"
|
|
232
|
+
authPrefix: v.string(), // e.g. "Bearer ", "Basic ", ""
|
|
233
|
+
encryptedMasterKey: v.string(),
|
|
234
|
+
rateLimitPerUser: v.number(), // requests per minute per user
|
|
235
|
+
rateLimitPerDay: v.number(), // requests per day per user
|
|
236
|
+
pricePerRequest: v.number(), // in USD cents
|
|
237
|
+
status: v.string(), // "draft" | "testing" | "live"
|
|
238
|
+
// Customer key passthrough settings
|
|
239
|
+
allowCustomerKeys: v.optional(v.boolean()), // Allow agents to pass their own API key (default: true)
|
|
240
|
+
requireCustomerKeys: v.optional(v.boolean()), // Require customer key, no master key fallback (default: false)
|
|
241
|
+
createdAt: v.number(),
|
|
242
|
+
updatedAt: v.number(),
|
|
243
|
+
publishedAt: v.optional(v.number()),
|
|
244
|
+
})
|
|
245
|
+
.index("by_providerId", ["providerId"])
|
|
246
|
+
.index("by_apiId", ["apiId"])
|
|
247
|
+
.index("by_status", ["status"]),
|
|
248
|
+
|
|
249
|
+
// Actions defined by providers for their Direct Call APIs
|
|
250
|
+
providerActions: defineTable({
|
|
251
|
+
directCallId: v.id("providerDirectCall"),
|
|
252
|
+
name: v.string(), // machine name, e.g. "send_sms"
|
|
253
|
+
displayName: v.string(), // human-friendly, e.g. "Send SMS"
|
|
254
|
+
description: v.string(),
|
|
255
|
+
method: v.string(), // "GET" | "POST" | "PUT" | "PATCH" | "DELETE"
|
|
256
|
+
path: v.string(), // e.g. "/v1/messages" or "/users/{userId}"
|
|
257
|
+
params: v.array(v.object({
|
|
258
|
+
name: v.string(),
|
|
259
|
+
type: v.string(), // "string" | "number" | "boolean" | "object"
|
|
260
|
+
required: v.boolean(),
|
|
261
|
+
description: v.string(),
|
|
262
|
+
default: v.optional(v.any()),
|
|
263
|
+
in: v.string(), // "body" | "query" | "path"
|
|
264
|
+
})),
|
|
265
|
+
responseMapping: v.array(v.object({
|
|
266
|
+
name: v.string(),
|
|
267
|
+
path: v.string(), // JSON path, e.g. "data.id" or "results[0].name"
|
|
268
|
+
})),
|
|
269
|
+
enabled: v.boolean(),
|
|
270
|
+
createdAt: v.number(),
|
|
271
|
+
updatedAt: v.number(),
|
|
272
|
+
})
|
|
273
|
+
.index("by_directCallId", ["directCallId"])
|
|
274
|
+
.index("by_directCallId_name", ["directCallId", "name"]),
|
|
275
|
+
|
|
276
|
+
// Usage logs for Direct Call actions
|
|
277
|
+
usageLog: defineTable({
|
|
278
|
+
userId: v.string(),
|
|
279
|
+
providerId: v.id("providers"),
|
|
280
|
+
directCallId: v.id("providerDirectCall"),
|
|
281
|
+
actionName: v.string(),
|
|
282
|
+
timestamp: v.number(),
|
|
283
|
+
success: v.boolean(),
|
|
284
|
+
latencyMs: v.number(),
|
|
285
|
+
creditsUsed: v.number(), // in USD cents
|
|
286
|
+
errorMessage: v.optional(v.string()),
|
|
287
|
+
})
|
|
288
|
+
.index("by_userId", ["userId"])
|
|
289
|
+
.index("by_providerId", ["providerId"])
|
|
290
|
+
.index("by_directCallId", ["directCallId"])
|
|
291
|
+
.index("by_timestamp", ["timestamp"])
|
|
292
|
+
.index("by_userId_providerId", ["userId", "providerId"])
|
|
293
|
+
.index("by_userId_timestamp", ["userId", "timestamp"]),
|
|
294
|
+
|
|
295
|
+
// ============================================
|
|
296
|
+
// WAITLIST (for Direct Call provider leads)
|
|
297
|
+
// ============================================
|
|
298
|
+
|
|
299
|
+
waitlist: defineTable({
|
|
300
|
+
email: v.string(),
|
|
301
|
+
type: v.string(), // "provider" | "agent" | "general"
|
|
302
|
+
source: v.optional(v.string()), // "landing", "docs", etc.
|
|
303
|
+
createdAt: v.number(),
|
|
304
|
+
})
|
|
305
|
+
.index("by_email", ["email"])
|
|
306
|
+
.index("by_type", ["type"]),
|
|
220
307
|
});
|