@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,617 +0,0 @@
1
- /**
2
- * APIClaw Dynamic Executor
3
- * Executes provider-configured actions via self-service Direct Call
4
- */
5
-
6
- import { decryptKey, validateBaseUrl } from './crypto.js';
7
-
8
- // Types for dynamic provider config
9
- export interface ProviderDirectCallConfig {
10
- _id: string;
11
- providerId: string;
12
- apiId: string;
13
- baseUrl: string;
14
- authType: 'bearer' | 'basic' | 'api_key' | 'none';
15
- authHeader: string;
16
- authPrefix: string;
17
- encryptedMasterKey: string;
18
- rateLimitPerUser: number;
19
- rateLimitPerDay: number;
20
- pricePerRequest: number;
21
- status: 'draft' | 'testing' | 'live';
22
- // Customer key passthrough settings
23
- allowCustomerKeys?: boolean; // Allow agents to pass their own API key (default: true)
24
- requireCustomerKeys?: boolean; // Require customer key, no master key fallback (default: false)
25
- }
26
-
27
- export interface ActionParam {
28
- name: string;
29
- type: 'string' | 'number' | 'boolean' | 'object';
30
- required: boolean;
31
- description: string;
32
- default?: unknown;
33
- in: 'body' | 'query' | 'path';
34
- }
35
-
36
- export interface ResponseMapping {
37
- name: string;
38
- path: string; // JSONPath expression
39
- }
40
-
41
- export interface ProviderAction {
42
- _id: string;
43
- directCallId: string;
44
- name: string;
45
- displayName: string;
46
- description: string;
47
- method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
48
- path: string;
49
- params: ActionParam[];
50
- responseMapping: ResponseMapping[];
51
- enabled: boolean;
52
- // Confirmation settings for costly actions
53
- requiresConfirmation?: boolean;
54
- estimatedCost?: string; // e.g., "~2-5 SEK per invoice"
55
- }
56
-
57
- export interface ExecuteResult {
58
- success: boolean;
59
- provider: string;
60
- action: string;
61
- data?: unknown;
62
- error?: string;
63
- cost?: number;
64
- latencyMs?: number;
65
- }
66
-
67
- interface UsageStats {
68
- minute: number;
69
- day: number;
70
- }
71
-
72
- // Convex HTTP API
73
- const CONVEX_URL = process.env.NEXT_PUBLIC_CONVEX_URL || 'https://brilliant-puffin-712.eu-west-1.convex.cloud';
74
-
75
- async function convexQuery<T>(path: string, args: Record<string, unknown>): Promise<T | null> {
76
- try {
77
- const res = await fetch(`${CONVEX_URL}/api/query`, {
78
- method: 'POST',
79
- headers: { 'Content-Type': 'application/json' },
80
- body: JSON.stringify({ path, args }),
81
- });
82
- if (!res.ok) {
83
- console.error(`Convex query failed: ${res.status}`);
84
- return null;
85
- }
86
- const data = await res.json() as { value?: T } | T;
87
- return (data && typeof data === 'object' && 'value' in data) ? data.value as T : data as T;
88
- } catch (error) {
89
- console.error('Convex query error:', error);
90
- return null;
91
- }
92
- }
93
-
94
- async function convexMutation(path: string, args: Record<string, unknown>): Promise<boolean> {
95
- try {
96
- const res = await fetch(`${CONVEX_URL}/api/mutation`, {
97
- method: 'POST',
98
- headers: { 'Content-Type': 'application/json' },
99
- body: JSON.stringify({ path, args }),
100
- });
101
- return res.ok;
102
- } catch (error) {
103
- console.error('Convex mutation error:', error);
104
- return false;
105
- }
106
- }
107
-
108
- /**
109
- * Check if a provider has dynamic (self-service) config
110
- */
111
- export async function hasDynamicConfig(providerId: string): Promise<boolean> {
112
- const config = await getProviderConfig(providerId);
113
- return config !== null && config.status === 'live';
114
- }
115
-
116
- /**
117
- * Fetch provider direct call configuration from Convex
118
- */
119
- export async function getProviderConfig(providerId: string): Promise<ProviderDirectCallConfig | null> {
120
- // First try by API slug (for agent execution by name)
121
- const bySlug = await convexQuery<ProviderDirectCallConfig>('directCall:getByApiSlug', { slug: providerId });
122
- // Check for error response from Convex (not a real config)
123
- const bySlugAny = bySlug as any;
124
- if (bySlug && !(bySlugAny.status === 'error' || bySlugAny.errorMessage)) {
125
- return bySlug;
126
- }
127
-
128
- // Only try direct provider ID lookup if it looks like a Convex ID (starts with valid prefix)
129
- // Convex IDs typically look like: k97xxx...
130
- if (providerId.match(/^[a-z][a-z0-9]{2,}/)) {
131
- const byId = await convexQuery<ProviderDirectCallConfig>('directCall:getDirectCallConfig', { providerId });
132
- const byIdAny = byId as any;
133
- if (byId && !(byIdAny.status === 'error' || byIdAny.errorMessage)) {
134
- return byId;
135
- }
136
- }
137
-
138
- return null;
139
- }
140
-
141
- /**
142
- * Fetch action configuration from Convex
143
- */
144
- export async function getActionConfig(providerId: string, actionName: string): Promise<ProviderAction | null> {
145
- // First get the direct call config to get directCallId
146
- const config = await getProviderConfig(providerId);
147
- if (!config) return null;
148
-
149
- return convexQuery<ProviderAction>('directCall:getActionByName', {
150
- directCallId: config._id,
151
- name: actionName
152
- });
153
- }
154
-
155
- /**
156
- * Check if a dynamic action requires confirmation (for costly actions)
157
- * Returns action config if confirmation required, null otherwise
158
- */
159
- export async function getDynamicConfirmationConfig(
160
- providerId: string,
161
- actionName: string
162
- ): Promise<{ required: boolean; action?: ProviderAction; estimatedCost?: string }> {
163
- const action = await getActionConfig(providerId, actionName);
164
-
165
- if (!action) {
166
- return { required: false };
167
- }
168
-
169
- if (action.requiresConfirmation) {
170
- return {
171
- required: true,
172
- action,
173
- estimatedCost: action.estimatedCost
174
- };
175
- }
176
-
177
- return { required: false };
178
- }
179
-
180
- /**
181
- * Get user's current usage for rate limiting
182
- */
183
- export async function getUserUsage(userId: string, providerId: string): Promise<UsageStats> {
184
- const usage = await convexQuery<UsageStats>('usage:getUserUsage', { userId, providerId });
185
- return usage || { minute: 0, day: 0 };
186
- }
187
-
188
- /**
189
- * Build the full URL with path and query parameters
190
- */
191
- export function buildUrl(
192
- baseUrl: string,
193
- path: string,
194
- params: Record<string, unknown>,
195
- paramDefs: ActionParam[]
196
- ): string {
197
- let finalPath = path;
198
- const queryParams = new URLSearchParams();
199
-
200
- for (const paramDef of paramDefs) {
201
- const value = params[paramDef.name] ?? paramDef.default;
202
- if (value === undefined) continue;
203
-
204
- const stringValue = typeof value === 'object' ? JSON.stringify(value) : String(value);
205
-
206
- if (paramDef.in === 'path') {
207
- // Replace path parameter: /users/{id} -> /users/123
208
- finalPath = finalPath.replace(`{${paramDef.name}}`, encodeURIComponent(stringValue));
209
- } else if (paramDef.in === 'query') {
210
- queryParams.set(paramDef.name, stringValue);
211
- }
212
- }
213
-
214
- // Ensure baseUrl doesn't end with slash and path starts with slash
215
- const cleanBase = baseUrl.replace(/\/$/, '');
216
- const cleanPath = finalPath.startsWith('/') ? finalPath : `/${finalPath}`;
217
-
218
- const queryString = queryParams.toString();
219
- return queryString ? `${cleanBase}${cleanPath}?${queryString}` : `${cleanBase}${cleanPath}`;
220
- }
221
-
222
- /**
223
- * Build request body from parameters
224
- */
225
- export function buildBody(
226
- params: Record<string, unknown>,
227
- paramDefs: ActionParam[]
228
- ): string | undefined {
229
- const bodyParams: Record<string, unknown> = {};
230
-
231
- for (const paramDef of paramDefs) {
232
- if (paramDef.in === 'body') {
233
- const value = params[paramDef.name] ?? paramDef.default;
234
- if (value !== undefined) {
235
- bodyParams[paramDef.name] = value;
236
- }
237
- }
238
- }
239
-
240
- if (Object.keys(bodyParams).length === 0) {
241
- return undefined;
242
- }
243
-
244
- return JSON.stringify(bodyParams);
245
- }
246
-
247
- /**
248
- * Build authentication headers based on auth type
249
- */
250
- export function buildAuthHeaders(
251
- config: ProviderDirectCallConfig,
252
- decryptedKey: string
253
- ): Record<string, string> {
254
- const headers: Record<string, string> = {
255
- 'Content-Type': 'application/json',
256
- 'User-Agent': 'APIClaw/1.0',
257
- };
258
-
259
- const headerName = config.authHeader || 'Authorization';
260
- const prefix = config.authPrefix || '';
261
-
262
- switch (config.authType) {
263
- case 'bearer':
264
- headers[headerName] = prefix ? `${prefix} ${decryptedKey}` : `Bearer ${decryptedKey}`;
265
- break;
266
-
267
- case 'basic':
268
- // Assume decryptedKey is "username:password"
269
- const base64 = Buffer.from(decryptedKey).toString('base64');
270
- headers[headerName] = `Basic ${base64}`;
271
- break;
272
-
273
- case 'api_key':
274
- // Custom header with the key directly
275
- headers[headerName] = prefix ? `${prefix} ${decryptedKey}` : decryptedKey;
276
- break;
277
-
278
- case 'none':
279
- // No auth header needed
280
- break;
281
- }
282
-
283
- return headers;
284
- }
285
-
286
- /**
287
- * Extract value from object using simple JSONPath-like expression
288
- * Supports: $.field, $.field.nested, $.array[0], $.array[*].field
289
- */
290
- export function extractJsonPath(data: unknown, path: string): unknown {
291
- if (!path.startsWith('$')) {
292
- return undefined;
293
- }
294
-
295
- const parts = path.slice(1).split('.').filter(Boolean);
296
- let current: unknown = data;
297
-
298
- for (const part of parts) {
299
- if (current === null || current === undefined) {
300
- return undefined;
301
- }
302
-
303
- // Handle array index: field[0] or field[*]
304
- const arrayMatch = part.match(/^(\w+)\[(\d+|\*)\]$/);
305
- if (arrayMatch) {
306
- const [, field, index] = arrayMatch;
307
- if (typeof current !== 'object') return undefined;
308
- current = (current as Record<string, unknown>)[field];
309
-
310
- if (!Array.isArray(current)) return undefined;
311
-
312
- if (index === '*') {
313
- // Return all elements (will need further processing)
314
- continue;
315
- } else {
316
- current = current[parseInt(index)];
317
- }
318
- } else {
319
- if (typeof current !== 'object') return undefined;
320
- current = (current as Record<string, unknown>)[part];
321
- }
322
- }
323
-
324
- return current;
325
- }
326
-
327
- /**
328
- * Map response data using configured response mappings
329
- */
330
- export function mapResponse(
331
- data: unknown,
332
- responseMapping: ResponseMapping[]
333
- ): Record<string, unknown> {
334
- if (!responseMapping || responseMapping.length === 0) {
335
- // No mapping configured, return raw data
336
- return { raw: data };
337
- }
338
-
339
- const result: Record<string, unknown> = {};
340
-
341
- for (const mapping of responseMapping) {
342
- const value = extractJsonPath(data, mapping.path);
343
- if (value !== undefined) {
344
- result[mapping.name] = value;
345
- }
346
- }
347
-
348
- return result;
349
- }
350
-
351
- /**
352
- * Log usage to Convex
353
- */
354
- export async function logUsage(params: {
355
- userId: string;
356
- providerId: string;
357
- actionName: string;
358
- timestamp: number;
359
- success: boolean;
360
- latencyMs: number;
361
- creditsUsed: number;
362
- }): Promise<void> {
363
- await convexMutation('usage:logUsage', params);
364
- }
365
-
366
- /**
367
- * Main function: Execute a dynamically configured action
368
- */
369
- export async function executeDynamicAction(
370
- providerId: string,
371
- actionName: string,
372
- params: Record<string, unknown>,
373
- userId: string,
374
- customerKey?: string
375
- ): Promise<ExecuteResult> {
376
- const startTime = Date.now();
377
-
378
- // 1. Get provider config
379
- const config = await getProviderConfig(providerId);
380
- if (!config) {
381
- return {
382
- success: false,
383
- provider: providerId,
384
- action: actionName,
385
- error: 'Provider not found'
386
- };
387
- }
388
-
389
- if (config.status !== 'live') {
390
- return {
391
- success: false,
392
- provider: providerId,
393
- action: actionName,
394
- error: 'Provider not available (not live)'
395
- };
396
- }
397
-
398
- // 2. Validate base URL (SSRF prevention)
399
- const urlValidation = validateBaseUrl(config.baseUrl);
400
- if (!urlValidation.valid) {
401
- return {
402
- success: false,
403
- provider: providerId,
404
- action: actionName,
405
- error: `Invalid provider URL: ${urlValidation.error}`
406
- };
407
- }
408
-
409
- // 3. Check rate limits
410
- const usage = await getUserUsage(userId, providerId);
411
- if (usage.minute >= config.rateLimitPerUser) {
412
- return {
413
- success: false,
414
- provider: providerId,
415
- action: actionName,
416
- error: 'Rate limit exceeded (per minute)'
417
- };
418
- }
419
- if (usage.day >= config.rateLimitPerDay) {
420
- return {
421
- success: false,
422
- provider: providerId,
423
- action: actionName,
424
- error: 'Rate limit exceeded (daily)'
425
- };
426
- }
427
-
428
- // 4. Get action config
429
- const action = await getActionConfig(providerId, actionName);
430
- if (!action) {
431
- return {
432
- success: false,
433
- provider: providerId,
434
- action: actionName,
435
- error: 'Action not found'
436
- };
437
- }
438
-
439
- if (!action.enabled) {
440
- return {
441
- success: false,
442
- provider: providerId,
443
- action: actionName,
444
- error: 'Action is disabled'
445
- };
446
- }
447
-
448
- // 5. Validate required parameters
449
- for (const paramDef of action.params) {
450
- if (paramDef.required && params[paramDef.name] === undefined && paramDef.default === undefined) {
451
- return {
452
- success: false,
453
- provider: providerId,
454
- action: actionName,
455
- error: `Missing required parameter: ${paramDef.name}`
456
- };
457
- }
458
- }
459
-
460
- // 6. Resolve API key (customer key takes priority over master key)
461
- let apiKey: string;
462
- let usingCustomerKey = false;
463
-
464
- // Check if provider requires customer keys (like CoAccept)
465
- const requiresCustomerKey = config.requireCustomerKeys === true;
466
- const allowsCustomerKey = config.allowCustomerKeys !== false; // Default true
467
-
468
- if (customerKey && allowsCustomerKey) {
469
- // Customer provided their own key - use it, skip usage tracking
470
- apiKey = customerKey;
471
- usingCustomerKey = true;
472
- } else if (requiresCustomerKey) {
473
- // Provider requires customer key but none provided
474
- return {
475
- success: false,
476
- provider: providerId,
477
- action: actionName,
478
- error: 'This provider requires your own API key. Pass it via customer_key parameter.'
479
- };
480
- } else if (config.encryptedMasterKey) {
481
- // Use provider's master key - track usage for billing
482
- try {
483
- apiKey = decryptKey(config.encryptedMasterKey);
484
- } catch (error) {
485
- console.error('Failed to decrypt provider key:', error);
486
- return {
487
- success: false,
488
- provider: providerId,
489
- action: actionName,
490
- error: 'Provider configuration error'
491
- };
492
- }
493
- } else {
494
- // No key available
495
- return {
496
- success: false,
497
- provider: providerId,
498
- action: actionName,
499
- error: 'No API key available. Provide your own key via customer_key parameter.'
500
- };
501
- }
502
-
503
- // 7. Build request
504
- const url = buildUrl(config.baseUrl, action.path, params, action.params);
505
- const headers = buildAuthHeaders(config, apiKey);
506
- const body = action.method === 'GET' ? undefined : buildBody(params, action.params);
507
-
508
- // 8. Execute request
509
- let response: Response;
510
- try {
511
- response = await fetch(url, {
512
- method: action.method,
513
- headers,
514
- body,
515
- });
516
- } catch (error) {
517
- const latencyMs = Date.now() - startTime;
518
- // Only log usage when using master key (for billing)
519
- if (!usingCustomerKey) {
520
- await logUsage({
521
- userId,
522
- providerId,
523
- actionName,
524
- timestamp: Date.now(),
525
- success: false,
526
- latencyMs,
527
- creditsUsed: 0
528
- });
529
- }
530
- return {
531
- success: false,
532
- provider: providerId,
533
- action: actionName,
534
- error: `Network error: ${error instanceof Error ? error.message : 'Unknown error'}`,
535
- latencyMs
536
- };
537
- }
538
-
539
- const latencyMs = Date.now() - startTime;
540
-
541
- // 9. Parse response
542
- let data: unknown;
543
- try {
544
- const contentType = response.headers.get('content-type');
545
- if (contentType?.includes('application/json')) {
546
- data = await response.json();
547
- } else {
548
- data = await response.text();
549
- }
550
- } catch {
551
- data = null;
552
- }
553
-
554
- // 10. Log usage (only when using master key for billing)
555
- if (!usingCustomerKey) {
556
- await logUsage({
557
- userId,
558
- providerId,
559
- actionName,
560
- timestamp: Date.now(),
561
- success: response.ok,
562
- latencyMs,
563
- creditsUsed: response.ok ? config.pricePerRequest : 0
564
- });
565
- }
566
-
567
- // 11. Handle error response
568
- if (!response.ok) {
569
- let errorMessage = `HTTP ${response.status}`;
570
- if (data && typeof data === 'object') {
571
- const errorObj = data as Record<string, unknown>;
572
- errorMessage = (errorObj.message as string) ||
573
- (errorObj.error as string) ||
574
- (errorObj.detail as string) ||
575
- errorMessage;
576
- }
577
- return {
578
- success: false,
579
- provider: providerId,
580
- action: actionName,
581
- error: errorMessage,
582
- latencyMs
583
- };
584
- }
585
-
586
- // 12. Map response and return
587
- const mappedData = mapResponse(data, action.responseMapping);
588
-
589
- return {
590
- success: true,
591
- provider: providerId,
592
- action: actionName,
593
- data: mappedData,
594
- cost: config.pricePerRequest,
595
- latencyMs
596
- };
597
- }
598
-
599
- /**
600
- * List available actions for a dynamic provider
601
- */
602
- export async function listDynamicActions(providerId: string): Promise<string[]> {
603
- const config = await getProviderConfig(providerId);
604
- if (!config || config.status !== 'live') {
605
- return [];
606
- }
607
-
608
- const actions = await convexQuery<ProviderAction[]>('directCall:getActions', {
609
- directCallId: config._id
610
- });
611
-
612
- if (!actions) return [];
613
-
614
- return actions
615
- .filter(a => a.enabled)
616
- .map(a => a.name);
617
- }