@nordsym/apiclaw 2.2.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 (176) 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/gateway-client.d.ts.map +1 -1
  5. package/dist/gateway-client.js +24 -2
  6. package/dist/gateway-client.js.map +1 -1
  7. package/dist/index.bundled.js +61263 -0
  8. package/dist/index.js +2 -2
  9. package/dist/index.js.map +1 -1
  10. package/package.json +7 -2
  11. package/.claude/settings.local.json +0 -13
  12. package/.env.prod +0 -1
  13. package/apiclaw-README.md +0 -494
  14. package/convex/_generated/api.d.ts +0 -145
  15. package/convex/_generated/api.js +0 -23
  16. package/convex/_generated/dataModel.d.ts +0 -60
  17. package/convex/_generated/server.d.ts +0 -143
  18. package/convex/_generated/server.js +0 -93
  19. package/convex/_listWorkspaces.ts +0 -13
  20. package/convex/adminActivate.ts +0 -53
  21. package/convex/adminStats.ts +0 -306
  22. package/convex/agents.ts +0 -939
  23. package/convex/analytics.ts +0 -187
  24. package/convex/apiKeys.ts +0 -220
  25. package/convex/backfillAnalytics.ts +0 -272
  26. package/convex/backfillSearchLogs.ts +0 -35
  27. package/convex/billing.ts +0 -834
  28. package/convex/capabilities.ts +0 -157
  29. package/convex/chains.ts +0 -1318
  30. package/convex/credits.ts +0 -211
  31. package/convex/crons.ts +0 -65
  32. package/convex/debugFilestackLogs.ts +0 -16
  33. package/convex/debugGetToken.ts +0 -18
  34. package/convex/directCall.ts +0 -713
  35. package/convex/earnProgress.ts +0 -753
  36. package/convex/email.ts +0 -329
  37. package/convex/feedback.ts +0 -265
  38. package/convex/funnel.ts +0 -431
  39. package/convex/guards.ts +0 -174
  40. package/convex/http.ts +0 -3756
  41. package/convex/inbound.ts +0 -32
  42. package/convex/logs.ts +0 -701
  43. package/convex/migrateFilestack.ts +0 -81
  44. package/convex/migratePartnersProd.ts +0 -174
  45. package/convex/migratePratham.ts +0 -126
  46. package/convex/migrateProviderWorkspaces.ts +0 -175
  47. package/convex/mou.ts +0 -91
  48. package/convex/nurture.ts +0 -355
  49. package/convex/providerKeys.ts +0 -289
  50. package/convex/providers.ts +0 -1135
  51. package/convex/purchases.ts +0 -183
  52. package/convex/ratelimit.ts +0 -104
  53. package/convex/schema.ts +0 -926
  54. package/convex/searchLogs.ts +0 -265
  55. package/convex/seedAPILayerAPIs.ts +0 -191
  56. package/convex/seedDirectCallConfigs.ts +0 -336
  57. package/convex/seedPratham.ts +0 -149
  58. package/convex/spendAlerts.ts +0 -442
  59. package/convex/stripeActions.ts +0 -607
  60. package/convex/teams.ts +0 -243
  61. package/convex/telemetry.ts +0 -81
  62. package/convex/tsconfig.json +0 -25
  63. package/convex/updateAPIStatus.ts +0 -44
  64. package/convex/usage.ts +0 -260
  65. package/convex/usageReports.ts +0 -357
  66. package/convex/waitlist.ts +0 -55
  67. package/convex/webhooks.ts +0 -494
  68. package/convex/workspaceSettings.ts +0 -143
  69. package/convex/workspaces.ts +0 -1331
  70. package/convex.json +0 -3
  71. package/direct-test.mjs +0 -51
  72. package/email-templates/filestack-provider-outreach.html +0 -162
  73. package/email-templates/partnership-template.html +0 -116
  74. package/email-templates/pratham-draft-preview.txt +0 -57
  75. package/email-templates/pratham-partnership-draft.html +0 -141
  76. package/reports/APIClaw-Session-Report-2026-04-05.pdf +0 -0
  77. package/reports/pipeline/PIPELINE-REPORT.json +0 -153
  78. package/reports/pipeline/acquire_apisguru.json +0 -17
  79. package/reports/pipeline/capabilities.json +0 -38
  80. package/reports/pipeline/discover_azure_recursive.json +0 -1551
  81. package/reports/pipeline/discover_github.json +0 -25
  82. package/reports/pipeline/discover_github_repos.json +0 -49
  83. package/reports/pipeline/discover_swaggerhub.json +0 -24
  84. package/reports/pipeline/discover_well_known.json +0 -23
  85. package/reports/pipeline/fetch_specs.json +0 -19
  86. package/reports/pipeline/generate_providers.json +0 -14
  87. package/reports/pipeline/match_registry.json +0 -11
  88. package/reports/pipeline/parse_specs.json +0 -17
  89. package/reports/pipeline/promote_candidates.json +0 -34
  90. package/reports/pipeline/validate.json +0 -30
  91. package/reports/pipeline/validate_smoke_details.json +0 -3835
  92. package/reports/session-report-2026-04-05.html +0 -433
  93. package/seed-apis-direct.mjs +0 -106
  94. package/src/access-control.ts +0 -174
  95. package/src/adapters/base.ts +0 -364
  96. package/src/adapters/claude-desktop.ts +0 -41
  97. package/src/adapters/cline.ts +0 -88
  98. package/src/adapters/continue.ts +0 -91
  99. package/src/adapters/cursor.ts +0 -43
  100. package/src/adapters/custom.ts +0 -188
  101. package/src/adapters/detect.ts +0 -202
  102. package/src/adapters/index.ts +0 -47
  103. package/src/adapters/windsurf.ts +0 -44
  104. package/src/bin-http.ts +0 -45
  105. package/src/bin.ts +0 -34
  106. package/src/capability-router.ts +0 -331
  107. package/src/chainExecutor.ts +0 -730
  108. package/src/chainResolver.test.ts +0 -246
  109. package/src/chainResolver.ts +0 -658
  110. package/src/cli/commands/demo.ts +0 -109
  111. package/src/cli/commands/doctor.ts +0 -435
  112. package/src/cli/commands/index.ts +0 -9
  113. package/src/cli/commands/login.ts +0 -203
  114. package/src/cli/commands/mcp-install.ts +0 -373
  115. package/src/cli/commands/restore.ts +0 -333
  116. package/src/cli/commands/setup.ts +0 -297
  117. package/src/cli/commands/uninstall.ts +0 -240
  118. package/src/cli/index.ts +0 -148
  119. package/src/cli.ts +0 -370
  120. package/src/confirmation.ts +0 -296
  121. package/src/credentials.ts +0 -455
  122. package/src/credits.ts +0 -329
  123. package/src/crypto.ts +0 -75
  124. package/src/discovery.ts +0 -568
  125. package/src/enterprise/env.ts +0 -156
  126. package/src/enterprise/index.ts +0 -7
  127. package/src/enterprise/script-generator.ts +0 -481
  128. package/src/execute-dynamic.ts +0 -617
  129. package/src/execute.ts +0 -2386
  130. package/src/funnel-client.ts +0 -168
  131. package/src/funnel.test.ts +0 -187
  132. package/src/gateway-client.ts +0 -192
  133. package/src/hivr-whitelist.ts +0 -110
  134. package/src/http-api.ts +0 -286
  135. package/src/http-server-minimal.ts +0 -154
  136. package/src/index.ts +0 -2702
  137. package/src/intelligent-gateway.ts +0 -339
  138. package/src/mcp-analytics.ts +0 -156
  139. package/src/metered.ts +0 -149
  140. package/src/open-apis-generated.ts +0 -157
  141. package/src/open-apis.ts +0 -558
  142. package/src/postinstall.ts +0 -40
  143. package/src/product-whitelist.ts +0 -246
  144. package/src/proxy.ts +0 -36
  145. package/src/registration-guard.ts +0 -117
  146. package/src/session.ts +0 -129
  147. package/src/stripe.ts +0 -497
  148. package/src/telemetry.ts +0 -71
  149. package/src/test.ts +0 -135
  150. package/src/types/convex-api.d.ts +0 -20
  151. package/src/types/convex-api.ts +0 -21
  152. package/src/types.ts +0 -109
  153. package/src/ui/colors.ts +0 -219
  154. package/src/ui/errors.ts +0 -394
  155. package/src/ui/index.ts +0 -17
  156. package/src/ui/prompts.ts +0 -390
  157. package/src/ui/spinner.ts +0 -325
  158. package/src/utils/backup.ts +0 -224
  159. package/src/utils/config.ts +0 -318
  160. package/src/utils/os.ts +0 -124
  161. package/src/utils/paths.ts +0 -203
  162. package/src/webhook.ts +0 -107
  163. package/test-10-working.cjs +0 -97
  164. package/test-14-final.cjs +0 -96
  165. package/test-actual-handlers.ts +0 -92
  166. package/test-apilayer-all-14.ts +0 -249
  167. package/test-apilayer-fixed.ts +0 -248
  168. package/test-direct-endpoints.ts +0 -174
  169. package/test-exact-endpoints.ts +0 -144
  170. package/test-final.ts +0 -83
  171. package/test-full-routing.ts +0 -100
  172. package/test-handlers-correct.ts +0 -217
  173. package/test-numverify-key.ts +0 -41
  174. package/test-via-handlers.ts +0 -92
  175. package/test-worldnews.mjs +0 -26
  176. 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
- }