@nordsym/apiclaw 1.5.17 → 1.5.19

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 (228) hide show
  1. package/convex/http.js.map +1 -1
  2. package/convex/http.ts +516 -0
  3. package/dist/analytics.d.ts +0 -4
  4. package/dist/analytics.d.ts.map +1 -1
  5. package/dist/analytics.js +0 -1
  6. package/dist/analytics.js.map +1 -1
  7. package/dist/bin.js +1 -1
  8. package/dist/cli/commands/mcp-install.d.ts.map +1 -1
  9. package/dist/cli/commands/mcp-install.js +8 -87
  10. package/dist/cli/commands/mcp-install.js.map +1 -1
  11. package/dist/cli/index.js +0 -7
  12. package/dist/credentials.d.ts.map +1 -1
  13. package/dist/credentials.js +38 -43
  14. package/dist/credentials.js.map +1 -1
  15. package/dist/discovery.d.ts.map +1 -1
  16. package/dist/discovery.js +82 -191
  17. package/dist/discovery.js.map +1 -1
  18. package/dist/http-api.d.ts.map +1 -1
  19. package/dist/http-api.js +33 -17
  20. package/dist/http-api.js.map +1 -1
  21. package/dist/proxy.js +1 -1
  22. package/dist/proxy.js.map +1 -1
  23. package/landing/next-env.d.ts +0 -1
  24. package/landing/src/app/api/auth/magic-link/route.ts +1 -1
  25. package/landing/src/app/auth/verify/page.tsx +0 -6
  26. package/landing/src/app/dashboard/verify/page.tsx +0 -6
  27. package/landing/src/app/join/page.tsx +0 -6
  28. package/landing/src/app/layout.tsx +2 -2
  29. package/landing/src/app/login/page.tsx +1 -1
  30. package/landing/src/app/mou/[partnerId]/page.tsx +0 -6
  31. package/landing/src/app/page.tsx +18 -39
  32. package/landing/src/app/providers/dashboard/[apiId]/actions/[actionId]/edit/page.tsx +0 -6
  33. package/landing/src/app/providers/dashboard/[apiId]/actions/new/page.tsx +0 -5
  34. package/landing/src/app/providers/dashboard/[apiId]/actions/page.tsx +0 -5
  35. package/landing/src/app/providers/dashboard/[apiId]/direct-call/page.tsx +1 -6
  36. package/landing/src/app/providers/dashboard/[apiId]/page.tsx +0 -5
  37. package/landing/src/app/providers/dashboard/[apiId]/test/page.tsx +0 -5
  38. package/landing/src/app/providers/dashboard/layout.tsx +6 -6
  39. package/landing/src/app/providers/dashboard/login/page.tsx +1 -1
  40. package/landing/src/app/providers/dashboard/page.tsx +1 -1
  41. package/landing/src/app/providers/dashboard/verify/page.tsx +0 -6
  42. package/landing/src/app/providers/layout.tsx +1 -1
  43. package/landing/src/app/upgrade/page.tsx +0 -6
  44. package/landing/src/app/workspace/page.tsx +0 -6
  45. package/landing/src/components/HeroTabs.tsx +2 -2
  46. package/landing/src/components/{Workspace.tsx → ProviderDashboard.tsx} +2 -2
  47. package/landing/src/components/VideoDemo.tsx +10 -21
  48. package/landing/src/lib/mock-data.ts +1 -1
  49. package/landing/src/lib/stats.json +1 -1
  50. package/package.json +3 -8
  51. package/src/analytics.ts +0 -5
  52. package/src/bin.ts +1 -1
  53. package/src/cli/commands/mcp-install.ts +8 -90
  54. package/src/cli/index.ts +0 -8
  55. package/src/credentials.ts +39 -44
  56. package/src/discovery.ts +82 -191
  57. package/src/http-api.ts +34 -18
  58. package/src/proxy.ts +1 -1
  59. package/APILAYER_STATUS_2026-03-24.md +0 -38
  60. package/CHANGELOG-WHITELIST-V2.md +0 -269
  61. package/HIVR-WHITELIST-STATUS.md +0 -205
  62. package/HIVR-WHITELIST.md +0 -148
  63. package/TERMINOLOGY-AUDIT.md +0 -99
  64. package/TERMINOLOGY-FIXED.md +0 -74
  65. package/VIDEO-DEMO-GUIDE.md +0 -82
  66. package/WHITELIST-ARCHITECTURE.md +0 -379
  67. package/api/discover.ts +0 -71
  68. package/api/health.ts +0 -20
  69. package/convex/adminActivate.d.ts +0 -3
  70. package/convex/adminActivate.js +0 -47
  71. package/convex/adminStats.d.ts +0 -3
  72. package/convex/adminStats.js +0 -42
  73. package/convex/agents.d.ts +0 -54
  74. package/convex/agents.js +0 -499
  75. package/convex/analytics.d.ts +0 -5
  76. package/convex/analytics.js +0 -166
  77. package/convex/billing.d.ts +0 -88
  78. package/convex/billing.js +0 -655
  79. package/convex/capabilities.d.ts +0 -9
  80. package/convex/capabilities.js +0 -145
  81. package/convex/chains.d.ts +0 -67
  82. package/convex/chains.js +0 -1042
  83. package/convex/credits.d.ts +0 -25
  84. package/convex/credits.js +0 -186
  85. package/convex/crons.d.ts +0 -3
  86. package/convex/crons.js +0 -17
  87. package/convex/directCall.d.ts +0 -72
  88. package/convex/directCall.js +0 -627
  89. package/convex/earnProgress.d.ts +0 -58
  90. package/convex/earnProgress.js +0 -649
  91. package/convex/email.d.ts +0 -14
  92. package/convex/email.js +0 -300
  93. package/convex/feedback.d.ts +0 -7
  94. package/convex/feedback.js +0 -227
  95. package/convex/http.d.ts +0 -3
  96. package/convex/http.js +0 -910
  97. package/convex/logs.d.ts +0 -38
  98. package/convex/logs.js +0 -487
  99. package/convex/mou.d.ts +0 -6
  100. package/convex/mou.js +0 -82
  101. package/convex/providerKeys.d.ts +0 -31
  102. package/convex/providerKeys.js +0 -257
  103. package/convex/providers.d.ts +0 -29
  104. package/convex/providers.js +0 -756
  105. package/convex/purchases.d.ts +0 -7
  106. package/convex/purchases.js +0 -157
  107. package/convex/ratelimit.d.ts +0 -4
  108. package/convex/ratelimit.js +0 -91
  109. package/convex/searchLogs.d.ts +0 -4
  110. package/convex/searchLogs.js +0 -129
  111. package/convex/spendAlerts.d.ts +0 -36
  112. package/convex/spendAlerts.js +0 -380
  113. package/convex/stripeActions.d.ts +0 -19
  114. package/convex/stripeActions.js +0 -411
  115. package/convex/teams.d.ts +0 -21
  116. package/convex/teams.js +0 -215
  117. package/convex/telemetry.d.ts +0 -4
  118. package/convex/telemetry.js +0 -74
  119. package/convex/usage.d.ts +0 -27
  120. package/convex/usage.js +0 -229
  121. package/convex/waitlist.d.ts +0 -4
  122. package/convex/waitlist.js +0 -49
  123. package/convex/webhooks.d.ts +0 -12
  124. package/convex/webhooks.js +0 -410
  125. package/convex/workspaces.d.ts +0 -29
  126. package/convex/workspaces.js +0 -880
  127. package/direct-test.mjs +0 -51
  128. package/dist/access-control.d.ts +0 -45
  129. package/dist/access-control.d.ts.map +0 -1
  130. package/dist/access-control.js +0 -142
  131. package/dist/access-control.js.map +0 -1
  132. package/dist/chain-types.d.ts +0 -187
  133. package/dist/chain-types.d.ts.map +0 -1
  134. package/dist/chain-types.js +0 -33
  135. package/dist/chain-types.js.map +0 -1
  136. package/dist/convex/adminActivate.js +0 -46
  137. package/dist/convex/adminStats.js +0 -41
  138. package/dist/convex/agents.js +0 -498
  139. package/dist/convex/analytics.js +0 -165
  140. package/dist/convex/billing.js +0 -654
  141. package/dist/convex/capabilities.js +0 -144
  142. package/dist/convex/chains.js +0 -1041
  143. package/dist/convex/credits.js +0 -185
  144. package/dist/convex/crons.js +0 -16
  145. package/dist/convex/directCall.js +0 -626
  146. package/dist/convex/earnProgress.js +0 -648
  147. package/dist/convex/email.js +0 -299
  148. package/dist/convex/feedback.js +0 -226
  149. package/dist/convex/http.js +0 -909
  150. package/dist/convex/logs.js +0 -486
  151. package/dist/convex/mou.js +0 -81
  152. package/dist/convex/providerKeys.js +0 -256
  153. package/dist/convex/providers.js +0 -755
  154. package/dist/convex/purchases.js +0 -156
  155. package/dist/convex/ratelimit.js +0 -90
  156. package/dist/convex/schema.js +0 -709
  157. package/dist/convex/searchLogs.js +0 -128
  158. package/dist/convex/spendAlerts.js +0 -379
  159. package/dist/convex/stripeActions.js +0 -410
  160. package/dist/convex/teams.js +0 -214
  161. package/dist/convex/telemetry.js +0 -73
  162. package/dist/convex/usage.js +0 -228
  163. package/dist/convex/waitlist.js +0 -48
  164. package/dist/convex/webhooks.js +0 -409
  165. package/dist/convex/workspaces.js +0 -879
  166. package/dist/hivr-whitelist.d.ts +0 -18
  167. package/dist/hivr-whitelist.d.ts.map +0 -1
  168. package/dist/hivr-whitelist.js +0 -95
  169. package/dist/hivr-whitelist.js.map +0 -1
  170. package/dist/http-server-minimal.d.ts +0 -7
  171. package/dist/http-server-minimal.d.ts.map +0 -1
  172. package/dist/http-server-minimal.js +0 -126
  173. package/dist/http-server-minimal.js.map +0 -1
  174. package/dist/product-whitelist.d.ts +0 -37
  175. package/dist/product-whitelist.d.ts.map +0 -1
  176. package/dist/product-whitelist.js +0 -203
  177. package/dist/product-whitelist.js.map +0 -1
  178. package/dist/src/analytics.js +0 -129
  179. package/dist/src/bin.js +0 -17
  180. package/dist/src/capability-router.js +0 -240
  181. package/dist/src/chainExecutor.js +0 -451
  182. package/dist/src/chainResolver.js +0 -518
  183. package/dist/src/cli/commands/doctor.js +0 -324
  184. package/dist/src/cli/commands/mcp-install.js +0 -255
  185. package/dist/src/cli/commands/restore.js +0 -259
  186. package/dist/src/cli/commands/setup.js +0 -205
  187. package/dist/src/cli/commands/uninstall.js +0 -188
  188. package/dist/src/cli/index.js +0 -111
  189. package/dist/src/cli.js +0 -302
  190. package/dist/src/confirmation.js +0 -240
  191. package/dist/src/credentials.js +0 -357
  192. package/dist/src/credits.js +0 -260
  193. package/dist/src/crypto.js +0 -66
  194. package/dist/src/discovery.js +0 -504
  195. package/dist/src/enterprise/env.js +0 -123
  196. package/dist/src/enterprise/script-generator.js +0 -460
  197. package/dist/src/execute-dynamic.js +0 -473
  198. package/dist/src/execute.js +0 -1727
  199. package/dist/src/index.js +0 -2062
  200. package/dist/src/metered.js +0 -80
  201. package/dist/src/open-apis.js +0 -276
  202. package/dist/src/proxy.js +0 -28
  203. package/dist/src/session.js +0 -86
  204. package/dist/src/stripe.js +0 -407
  205. package/dist/src/telemetry.js +0 -49
  206. package/dist/src/types.js +0 -2
  207. package/dist/src/utils/backup.js +0 -181
  208. package/dist/src/utils/config.js +0 -220
  209. package/dist/src/utils/os.js +0 -105
  210. package/dist/src/utils/paths.js +0 -159
  211. package/landing/pages/api/discover.ts +0 -43
  212. package/landing/pages/api/health.ts +0 -20
  213. package/scripts/test-whitelist-v2.sh +0 -128
  214. package/src/access-control.ts +0 -174
  215. package/src/hivr-whitelist.ts +0 -110
  216. package/src/http-server-minimal.ts +0 -154
  217. package/src/product-whitelist.ts +0 -246
  218. package/test-actual-handlers.ts +0 -92
  219. package/test-apilayer-all-14.ts +0 -249
  220. package/test-apilayer-fixed.ts +0 -248
  221. package/test-direct-endpoints.ts +0 -174
  222. package/test-exact-endpoints.ts +0 -144
  223. package/test-final.ts +0 -83
  224. package/test-full-routing.ts +0 -100
  225. package/test-handlers-correct.ts +0 -217
  226. package/test-numverify-key.ts +0 -41
  227. package/test-via-handlers.ts +0 -92
  228. package/test-worldnews.mjs +0 -26
@@ -1,1727 +0,0 @@
1
- /**
2
- * APIClaw Direct Call - Execute API calls through connected providers
3
- */
4
- import { getCredentials } from './credentials.js';
5
- import { callProxy, PROXY_PROVIDERS } from './proxy.js';
6
- import { executeDynamicAction, hasDynamicConfig, listDynamicActions } from './execute-dynamic.js';
7
- // Re-export chain execution
8
- export { executeChain } from './chainExecutor.js';
9
- export { resolveReferences, validateReferences, extractReferences, } from './chainResolver.js';
10
- // Error codes for structured error responses
11
- const ERROR_CODES = {
12
- RATE_LIMITED: 'RATE_LIMITED',
13
- SERVICE_UNAVAILABLE: 'SERVICE_UNAVAILABLE',
14
- UNAUTHORIZED: 'UNAUTHORIZED',
15
- FORBIDDEN: 'FORBIDDEN',
16
- NOT_FOUND: 'NOT_FOUND',
17
- BAD_REQUEST: 'BAD_REQUEST',
18
- TIMEOUT: 'TIMEOUT',
19
- NETWORK_ERROR: 'NETWORK_ERROR',
20
- PROVIDER_ERROR: 'PROVIDER_ERROR',
21
- INVALID_PARAMS: 'INVALID_PARAMS',
22
- NO_CREDENTIALS: 'NO_CREDENTIALS',
23
- UNKNOWN_PROVIDER: 'UNKNOWN_PROVIDER',
24
- UNKNOWN_ACTION: 'UNKNOWN_ACTION',
25
- MAX_RETRIES_EXCEEDED: 'MAX_RETRIES_EXCEEDED',
26
- };
27
- // Retry configuration
28
- const RETRY_CONFIG = {
29
- maxRetries: 3,
30
- baseDelayMs: 1000, // Start with 1 second
31
- maxDelayMs: 30000, // Cap at 30 seconds
32
- retryableStatusCodes: [429, 503, 502, 504], // Rate limit + service unavailable variants
33
- };
34
- /**
35
- * Calculate exponential backoff delay with jitter
36
- */
37
- function calculateBackoff(attempt) {
38
- const exponentialDelay = RETRY_CONFIG.baseDelayMs * Math.pow(2, attempt);
39
- const jitter = Math.random() * 0.3 * exponentialDelay; // 0-30% jitter
40
- return Math.min(exponentialDelay + jitter, RETRY_CONFIG.maxDelayMs);
41
- }
42
- /**
43
- * Sleep for a given number of milliseconds
44
- */
45
- function sleep(ms) {
46
- return new Promise(resolve => setTimeout(resolve, ms));
47
- }
48
- /**
49
- * Map HTTP status code to error code
50
- */
51
- function statusToErrorCode(status) {
52
- switch (status) {
53
- case 400: return ERROR_CODES.BAD_REQUEST;
54
- case 401: return ERROR_CODES.UNAUTHORIZED;
55
- case 403: return ERROR_CODES.FORBIDDEN;
56
- case 404: return ERROR_CODES.NOT_FOUND;
57
- case 429: return ERROR_CODES.RATE_LIMITED;
58
- case 502:
59
- case 503:
60
- case 504: return ERROR_CODES.SERVICE_UNAVAILABLE;
61
- default: return ERROR_CODES.PROVIDER_ERROR;
62
- }
63
- }
64
- /**
65
- * Check if a response status code is retryable
66
- */
67
- function isRetryableStatus(status) {
68
- return RETRY_CONFIG.retryableStatusCodes.includes(status);
69
- }
70
- /**
71
- * Fetch with automatic retry for transient failures (429, 503)
72
- */
73
- async function fetchWithRetry(url, options, context) {
74
- let lastError = null;
75
- let lastResponse = null;
76
- for (let attempt = 0; attempt <= RETRY_CONFIG.maxRetries; attempt++) {
77
- try {
78
- const response = await fetch(url, options);
79
- // Check if we should retry
80
- if (isRetryableStatus(response.status) && attempt < RETRY_CONFIG.maxRetries) {
81
- lastResponse = response;
82
- const delay = calculateBackoff(attempt);
83
- // Check for Retry-After header
84
- const retryAfter = response.headers.get('Retry-After');
85
- const retryDelay = retryAfter
86
- ? (parseInt(retryAfter) * 1000 || delay)
87
- : delay;
88
- console.log(`[APIClaw] ${context.provider}/${context.action}: Got ${response.status}, retrying in ${Math.round(retryDelay)}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries})`);
89
- await sleep(Math.min(retryDelay, RETRY_CONFIG.maxDelayMs));
90
- continue;
91
- }
92
- return response;
93
- }
94
- catch (error) {
95
- lastError = error;
96
- // Retry on network errors
97
- if (attempt < RETRY_CONFIG.maxRetries) {
98
- const delay = calculateBackoff(attempt);
99
- console.log(`[APIClaw] ${context.provider}/${context.action}: Network error, retrying in ${Math.round(delay)}ms (attempt ${attempt + 1}/${RETRY_CONFIG.maxRetries})`);
100
- await sleep(delay);
101
- continue;
102
- }
103
- }
104
- }
105
- // If we have a response (even if error status), return it for proper error handling
106
- if (lastResponse) {
107
- return lastResponse;
108
- }
109
- // All retries exhausted with network errors
110
- throw lastError || new Error('Max retries exceeded');
111
- }
112
- /**
113
- * Normalize response by extracting common fields to top-level
114
- * Makes it easier for agents to access key data without digging into provider-specific structures
115
- */
116
- function normalizeResponse(result) {
117
- if (!result.success || !result.data)
118
- return result;
119
- const data = result.data;
120
- const normalized = { ...result };
121
- // Extract URL (various field names across providers)
122
- const urlFields = ['url', 'audioUrl', 'audio_url', 'output_url', 'image_url', 'video_url', 'file_url', 'download_url'];
123
- for (const field of urlFields) {
124
- if (data[field] && typeof data[field] === 'string') {
125
- normalized.url = data[field];
126
- break;
127
- }
128
- }
129
- // Handle array outputs (e.g., Replicate returns output: ["url1", "url2"])
130
- if (!normalized.url && Array.isArray(data.output) && data.output.length > 0 && typeof data.output[0] === 'string') {
131
- normalized.url = data.output[0];
132
- }
133
- // Extract ID
134
- const idFields = ['id', 'sid', 'message_id', 'prediction_id', 'job_id', 'request_id'];
135
- for (const field of idFields) {
136
- if (data[field] && (typeof data[field] === 'string' || typeof data[field] === 'number')) {
137
- normalized.id = String(data[field]);
138
- break;
139
- }
140
- }
141
- // Extract content (for LLM/text responses)
142
- const contentFields = ['content', 'text', 'message', 'response', 'output'];
143
- for (const field of contentFields) {
144
- if (data[field] && typeof data[field] === 'string') {
145
- normalized.content = data[field];
146
- break;
147
- }
148
- }
149
- // Extract status
150
- const statusFields = ['status', 'state'];
151
- for (const field of statusFields) {
152
- if (data[field] && typeof data[field] === 'string') {
153
- normalized.status = data[field];
154
- break;
155
- }
156
- }
157
- return normalized;
158
- }
159
- // Mock response generators for dry-run mode
160
- const mockResponses = {
161
- '46elks': {
162
- send_sms: (params) => ({
163
- id: 'mock_sms_' + Date.now(),
164
- to: params.to,
165
- from: params.from || 'APIClaw',
166
- message: params.message,
167
- status: 'delivered',
168
- cost: 5200, // ~0.52 SEK in microöre
169
- }),
170
- },
171
- twilio: {
172
- send_sms: (params) => ({
173
- sid: 'SMmock' + Date.now(),
174
- to: params.to,
175
- from: params.from || '+15017122661',
176
- body: params.message,
177
- status: 'queued',
178
- }),
179
- },
180
- brave_search: {
181
- search: (params) => ({
182
- query: params.query,
183
- results: [
184
- { title: 'Mock Result 1', url: 'https://example.com/1', description: 'This is a mock search result for dry-run testing.' },
185
- { title: 'Mock Result 2', url: 'https://example.com/2', description: 'Another mock result to simulate search.' },
186
- ],
187
- total: 2,
188
- }),
189
- },
190
- resend: {
191
- send_email: (params) => ({
192
- id: 'mock_email_' + Date.now(),
193
- to: params.to,
194
- from: params.from || 'APIClaw <noreply@apiclaw.nordsym.com>',
195
- subject: params.subject,
196
- status: 'sent',
197
- }),
198
- },
199
- openrouter: {
200
- chat: (params) => ({
201
- content: '[DRY-RUN] This is a mock response. In production, this would be generated by ' + (params.model || 'anthropic/claude-3-haiku'),
202
- model: params.model || 'anthropic/claude-3-haiku',
203
- usage: { prompt_tokens: 10, completion_tokens: 20, total_tokens: 30 },
204
- }),
205
- },
206
- elevenlabs: {
207
- text_to_speech: (params) => ({
208
- audio_base64: 'W0RSWV9SVU5fTU9DS19BVURJT10=', // "[DRY_RUN_MOCK_AUDIO]" in base64
209
- format: 'mp3',
210
- text_length: params.text?.length || 0,
211
- }),
212
- },
213
- replicate: {
214
- run: (params) => ({
215
- status: 'succeeded',
216
- output: ['https://example.com/mock-image.png'],
217
- model: params.model,
218
- metrics: { predict_time: 2.5 },
219
- }),
220
- list_models: () => ({
221
- models: [
222
- { name: 'stability-ai/sdxl', description: 'Mock model listing' },
223
- ],
224
- message: 'DRY-RUN: Would list available models',
225
- }),
226
- },
227
- firecrawl: {
228
- scrape: (params) => ({
229
- markdown: `# Mock Scrape of ${params.url}\n\nThis is a dry-run mock of scraped content.`,
230
- metadata: { title: 'Mock Page', url: params.url },
231
- }),
232
- crawl: (params) => ({
233
- id: 'crawl_mock_' + Date.now(),
234
- status: 'started',
235
- message: 'DRY-RUN: Crawl job would be started',
236
- }),
237
- map: (params) => ({
238
- links: ['https://example.com/page1', 'https://example.com/page2'],
239
- }),
240
- },
241
- github: {
242
- search_repos: (params) => ({
243
- total: 2,
244
- repos: [
245
- { name: 'mock/repo', description: 'Mock repository', stars: 100, url: 'https://github.com/mock/repo', language: 'TypeScript' },
246
- ],
247
- }),
248
- get_repo: (params) => ({
249
- name: `${params.owner}/${params.repo}`,
250
- description: 'Mock repository details',
251
- stars: 100,
252
- forks: 10,
253
- language: 'TypeScript',
254
- url: `https://github.com/${params.owner}/${params.repo}`,
255
- }),
256
- list_issues: () => ({
257
- issues: [{ number: 1, title: 'Mock Issue', state: 'open', user: 'mock-user' }],
258
- }),
259
- create_issue: (params) => ({
260
- number: 1,
261
- url: `https://github.com/${params.owner}/${params.repo}/issues/1`,
262
- }),
263
- get_file: (params) => ({
264
- name: params.path.split('/').pop(),
265
- path: params.path,
266
- size: 100,
267
- content: '// Mock file content for dry-run',
268
- }),
269
- },
270
- e2b: {
271
- run_code: (params) => ({
272
- text: 'DRY-RUN: Code would execute: ' + (params.code?.substring(0, 50) || ''),
273
- logs: { stdout: [], stderr: [] },
274
- results: [],
275
- }),
276
- run_shell: (params) => ({
277
- stdout: 'DRY-RUN: Would run: ' + params.command,
278
- stderr: '',
279
- exitCode: 0,
280
- }),
281
- },
282
- };
283
- // API endpoint info for dry-run
284
- const apiEndpoints = {
285
- '46elks': {
286
- send_sms: { url: 'https://api.46elks.com/a1/sms', method: 'POST', estimatedCost: '~0.35-0.52 SEK' },
287
- },
288
- twilio: {
289
- send_sms: { url: 'https://api.twilio.com/2010-04-01/Accounts/{accountSid}/Messages.json', method: 'POST', estimatedCost: '~$0.0079/SMS' },
290
- },
291
- brave_search: {
292
- search: { url: 'https://api.search.brave.com/res/v1/web/search', method: 'GET' },
293
- },
294
- resend: {
295
- send_email: { url: 'https://api.resend.com/emails', method: 'POST' },
296
- },
297
- openrouter: {
298
- chat: { url: 'https://openrouter.ai/api/v1/chat/completions', method: 'POST', estimatedCost: 'varies by model' },
299
- },
300
- elevenlabs: {
301
- text_to_speech: { url: 'https://api.elevenlabs.io/v1/text-to-speech/{voice_id}', method: 'POST', estimatedCost: '~$0.30/1000 chars' },
302
- },
303
- replicate: {
304
- run: { url: 'https://api.replicate.com/v1/predictions', method: 'POST', estimatedCost: 'varies by model' },
305
- list_models: { url: 'https://api.replicate.com/v1/models', method: 'GET' },
306
- },
307
- firecrawl: {
308
- scrape: { url: 'https://api.firecrawl.dev/v1/scrape', method: 'POST' },
309
- crawl: { url: 'https://api.firecrawl.dev/v1/crawl', method: 'POST' },
310
- map: { url: 'https://api.firecrawl.dev/v1/map', method: 'POST' },
311
- },
312
- github: {
313
- search_repos: { url: 'https://api.github.com/search/repositories', method: 'GET' },
314
- get_repo: { url: 'https://api.github.com/repos/{owner}/{repo}', method: 'GET' },
315
- list_issues: { url: 'https://api.github.com/repos/{owner}/{repo}/issues', method: 'GET' },
316
- create_issue: { url: 'https://api.github.com/repos/{owner}/{repo}/issues', method: 'POST' },
317
- get_file: { url: 'https://api.github.com/repos/{owner}/{repo}/contents/{path}', method: 'GET' },
318
- },
319
- e2b: {
320
- run_code: { url: 'https://api.e2b.dev/v1/sandboxes', method: 'POST' },
321
- run_shell: { url: 'https://api.e2b.dev/v1/sandboxes', method: 'POST' },
322
- },
323
- apilayer: {
324
- exchange_rates: { url: 'https://api.apilayer.com/exchangerates_data/latest', method: 'GET' },
325
- market_data: { url: 'http://api.marketstack.com/v1/eod', method: 'GET' },
326
- aviation: { url: 'http://api.aviationstack.com/v1/flights', method: 'GET' },
327
- pdf_generate: { url: 'https://api.pdflayer.com/api', method: 'POST' },
328
- screenshot: { url: 'https://api.screenshotlayer.com/api/capture', method: 'GET' },
329
- verify_email: { url: 'https://api.apilayer.com/email_verification/check', method: 'GET' },
330
- verify_number: { url: 'https://api.apilayer.com/number_verification/validate', method: 'GET' },
331
- vat_check: { url: 'https://apilayer.net/api/validate', method: 'GET' },
332
- world_news: { url: 'https://api.apilayer.com/world_news/extract-news', method: 'GET' },
333
- finance_news: { url: 'https://api.apilayer.com/financelayer/news', method: 'GET' },
334
- scrape: { url: 'https://api.apilayer.com/adv_scraper/scraper', method: 'GET' },
335
- image_crop: { url: 'https://api.apilayer.com/smart_crop/url', method: 'POST' },
336
- skills: { url: 'https://api.promptapi.com/skills', method: 'GET' },
337
- form_submit: { url: 'https://api.apilayer.com/form_api/{endpoint}', method: 'POST' },
338
- },
339
- };
340
- /**
341
- * Generate a dry-run result showing what would be sent without making actual API calls
342
- */
343
- export function generateDryRun(providerId, action, params) {
344
- const endpoint = apiEndpoints[providerId]?.[action] || { url: 'unknown', method: 'POST' };
345
- const mockGen = mockResponses[providerId]?.[action];
346
- const mockData = mockGen ? mockGen(params) : { message: 'Mock response for ' + action };
347
- // Build what would be sent
348
- const headers = {
349
- 'Content-Type': 'application/json',
350
- };
351
- // Add auth header hints
352
- if (['46elks', 'twilio'].includes(providerId)) {
353
- headers['Authorization'] = 'Basic [base64(username:password)]';
354
- }
355
- else if (['brave_search'].includes(providerId)) {
356
- headers['X-Subscription-Token'] = '[API_KEY]';
357
- }
358
- else if (['resend', 'openrouter', 'replicate', 'firecrawl'].includes(providerId)) {
359
- headers['Authorization'] = 'Bearer [API_KEY]';
360
- }
361
- else if (providerId === 'elevenlabs') {
362
- headers['xi-api-key'] = '[API_KEY]';
363
- }
364
- else if (providerId === 'github') {
365
- headers['Authorization'] = 'Bearer [GITHUB_TOKEN]';
366
- headers['User-Agent'] = 'APIClaw';
367
- }
368
- // Determine body based on method
369
- let body = undefined;
370
- if (endpoint.method === 'POST') {
371
- if (providerId === '46elks') {
372
- body = { from: params.from || 'APIClaw', to: params.to, message: params.message };
373
- }
374
- else if (providerId === 'twilio') {
375
- body = { From: params.from, To: params.to, Body: params.message };
376
- }
377
- else {
378
- body = params;
379
- }
380
- }
381
- const notes = [
382
- '⚠️ DRY-RUN MODE: No actual API call was made',
383
- 'This shows what WOULD be sent if you remove dry_run: true',
384
- ];
385
- if (endpoint.estimatedCost) {
386
- notes.push(`Estimated cost: ${endpoint.estimatedCost}`);
387
- }
388
- return {
389
- dry_run: true,
390
- provider: providerId,
391
- action,
392
- would_send: {
393
- url: endpoint.url,
394
- method: endpoint.method,
395
- headers,
396
- ...(body ? { body } : {}),
397
- },
398
- mock_response: {
399
- success: true,
400
- data: mockData,
401
- ...(endpoint.estimatedCost ? { estimated_cost: endpoint.estimatedCost } : {}),
402
- },
403
- notes,
404
- };
405
- }
406
- /**
407
- * Create a structured error result with error code
408
- */
409
- function createErrorResult(provider, action, error, code, status) {
410
- return {
411
- success: false,
412
- provider,
413
- action,
414
- error,
415
- code,
416
- };
417
- }
418
- // Helper to safely access properties
419
- function safeGet(obj, ...keys) {
420
- let current = obj;
421
- for (const key of keys) {
422
- if (current && typeof current === 'object' && key in current) {
423
- current = current[key];
424
- }
425
- else {
426
- return undefined;
427
- }
428
- }
429
- return current;
430
- }
431
- // Provider action handlers
432
- const handlers = {
433
- // 46elks - Swedish SMS/Voice
434
- '46elks': {
435
- send_sms: async (params, creds) => {
436
- const { to, message, from = 'APIClaw' } = params;
437
- if (!to || !message) {
438
- return createErrorResult('46elks', 'send_sms', 'Missing required params: to, message', ERROR_CODES.INVALID_PARAMS);
439
- }
440
- const auth = Buffer.from(`${creds.username}:${creds.password}`).toString('base64');
441
- const response = await fetchWithRetry('https://api.46elks.com/a1/sms', {
442
- method: 'POST',
443
- headers: {
444
- 'Authorization': `Basic ${auth}`,
445
- 'Content-Type': 'application/x-www-form-urlencoded',
446
- },
447
- body: new URLSearchParams({ from, to, message }),
448
- }, { provider: '46elks', action: 'send_sms' });
449
- const data = await response.json();
450
- if (!response.ok) {
451
- return createErrorResult('46elks', 'send_sms', data.message || 'SMS failed', statusToErrorCode(response.status));
452
- }
453
- return {
454
- success: true,
455
- provider: '46elks',
456
- action: 'send_sms',
457
- data: { id: data.id, to: data.to, cost: data.cost },
458
- cost: parseInt(String(data.cost)) / 10000000 // Convert microöre to SEK
459
- };
460
- },
461
- },
462
- // Twilio - Global SMS/Voice
463
- twilio: {
464
- send_sms: async (params, creds) => {
465
- const { to, message, from } = params;
466
- if (!to || !message) {
467
- return createErrorResult('twilio', 'send_sms', 'Missing required params: to, message', ERROR_CODES.INVALID_PARAMS);
468
- }
469
- const auth = Buffer.from(`${creds.username}:${creds.password}`).toString('base64');
470
- const fromNumber = from || creds.from_number || '+15017122661';
471
- const response = await fetchWithRetry(`https://api.twilio.com/2010-04-01/Accounts/${creds.username}/Messages.json`, {
472
- method: 'POST',
473
- headers: {
474
- 'Authorization': `Basic ${auth}`,
475
- 'Content-Type': 'application/x-www-form-urlencoded',
476
- },
477
- body: new URLSearchParams({ From: fromNumber, To: to, Body: message }),
478
- }, { provider: 'twilio', action: 'send_sms' });
479
- const data = await response.json();
480
- if (!response.ok) {
481
- return createErrorResult('twilio', 'send_sms', data.message || 'SMS failed', statusToErrorCode(response.status));
482
- }
483
- return {
484
- success: true,
485
- provider: 'twilio',
486
- action: 'send_sms',
487
- data: { sid: data.sid, to: data.to, status: data.status }
488
- };
489
- },
490
- },
491
- // Brave Search
492
- brave_search: {
493
- search: async (params, creds) => {
494
- const { query, count = 5 } = params;
495
- if (!query) {
496
- return createErrorResult('brave_search', 'search', 'Missing required param: query', ERROR_CODES.INVALID_PARAMS);
497
- }
498
- const url = new URL('https://api.search.brave.com/res/v1/web/search');
499
- url.searchParams.set('q', query);
500
- url.searchParams.set('count', count.toString());
501
- const response = await fetchWithRetry(url.toString(), {
502
- headers: { 'X-Subscription-Token': creds.api_key },
503
- }, { provider: 'brave_search', action: 'search' });
504
- const data = await response.json();
505
- if (!response.ok) {
506
- return createErrorResult('brave_search', 'search', data.message || 'Search failed', statusToErrorCode(response.status));
507
- }
508
- const webData = data.web;
509
- const rawResults = webData?.results || [];
510
- const results = rawResults.map((r) => ({
511
- title: r.title,
512
- url: r.url,
513
- description: r.description,
514
- }));
515
- return {
516
- success: true,
517
- provider: 'brave_search',
518
- action: 'search',
519
- data: { query, results, total: results.length }
520
- };
521
- },
522
- },
523
- // Resend - Email
524
- resend: {
525
- send_email: async (params, creds) => {
526
- const { to, subject, html, text, from = 'APIClaw <noreply@apiclaw.nordsym.com>' } = params;
527
- if (!to || !subject || (!html && !text)) {
528
- return createErrorResult('resend', 'send_email', 'Missing required params: to, subject, html or text', ERROR_CODES.INVALID_PARAMS);
529
- }
530
- const response = await fetchWithRetry('https://api.resend.com/emails', {
531
- method: 'POST',
532
- headers: {
533
- 'Authorization': `Bearer ${creds.api_key}`,
534
- 'Content-Type': 'application/json',
535
- },
536
- body: JSON.stringify({ from, to, subject, html, text }),
537
- }, { provider: 'resend', action: 'send_email' });
538
- const data = await response.json();
539
- if (!response.ok) {
540
- return createErrorResult('resend', 'send_email', data.message || 'Email failed', statusToErrorCode(response.status));
541
- }
542
- return {
543
- success: true,
544
- provider: 'resend',
545
- action: 'send_email',
546
- data: { id: data.id }
547
- };
548
- },
549
- },
550
- // OpenRouter - AI Models
551
- openrouter: {
552
- chat: async (params, creds) => {
553
- const { messages, model = 'anthropic/claude-3-haiku', max_tokens = 1000 } = params;
554
- if (!messages || !Array.isArray(messages)) {
555
- return createErrorResult('openrouter', 'chat', 'Missing required param: messages (array)', ERROR_CODES.INVALID_PARAMS);
556
- }
557
- const response = await fetchWithRetry('https://openrouter.ai/api/v1/chat/completions', {
558
- method: 'POST',
559
- headers: {
560
- 'Authorization': `Bearer ${creds.api_key}`,
561
- 'Content-Type': 'application/json',
562
- 'HTTP-Referer': 'https://apiclaw.nordsym.com',
563
- },
564
- body: JSON.stringify({ model, messages, max_tokens }),
565
- }, { provider: 'openrouter', action: 'chat' });
566
- const data = await response.json();
567
- if (!response.ok) {
568
- const errorData = data.error;
569
- return createErrorResult('openrouter', 'chat', errorData?.message || 'Chat failed', statusToErrorCode(response.status));
570
- }
571
- const choices = data.choices;
572
- const firstChoice = choices?.[0];
573
- const message = firstChoice?.message;
574
- return {
575
- success: true,
576
- provider: 'openrouter',
577
- action: 'chat',
578
- data: {
579
- content: message?.content,
580
- model: data.model,
581
- usage: data.usage
582
- }
583
- };
584
- },
585
- },
586
- // ElevenLabs - Text-to-Speech
587
- elevenlabs: {
588
- text_to_speech: async (params, creds) => {
589
- const { text, voice_id = '21m00Tcm4TlvDq8ikWAM', model_id = 'eleven_monolingual_v1' } = params;
590
- if (!text) {
591
- return createErrorResult('elevenlabs', 'text_to_speech', 'Missing required param: text', ERROR_CODES.INVALID_PARAMS);
592
- }
593
- const response = await fetchWithRetry(`https://api.elevenlabs.io/v1/text-to-speech/${voice_id}`, {
594
- method: 'POST',
595
- headers: {
596
- 'xi-api-key': creds.api_key,
597
- 'Content-Type': 'application/json',
598
- },
599
- body: JSON.stringify({ text, model_id }),
600
- }, { provider: 'elevenlabs', action: 'text_to_speech' });
601
- if (!response.ok) {
602
- const error = await response.json().catch(() => ({}));
603
- return createErrorResult('elevenlabs', 'text_to_speech', error.detail || 'TTS failed', statusToErrorCode(response.status));
604
- }
605
- // Return audio as base64
606
- const buffer = await response.arrayBuffer();
607
- const base64 = Buffer.from(buffer).toString('base64');
608
- return {
609
- success: true,
610
- provider: 'elevenlabs',
611
- action: 'text_to_speech',
612
- data: {
613
- audio_base64: base64,
614
- format: 'mp3',
615
- text_length: text.length
616
- }
617
- };
618
- },
619
- },
620
- // Replicate - Run any AI model (images, audio, video, text)
621
- replicate: {
622
- run: async (params, creds) => {
623
- const { model, input } = params;
624
- if (!model) {
625
- return createErrorResult('replicate', 'run', 'Missing required param: model (e.g., "stability-ai/sdxl:...")', ERROR_CODES.INVALID_PARAMS);
626
- }
627
- if (!input) {
628
- return createErrorResult('replicate', 'run', 'Missing required param: input (object with model inputs)', ERROR_CODES.INVALID_PARAMS);
629
- }
630
- // Parse model into owner/name and version
631
- const [modelPath, version] = model.split(':');
632
- // Create prediction
633
- const response = await fetchWithRetry('https://api.replicate.com/v1/predictions', {
634
- method: 'POST',
635
- headers: {
636
- 'Authorization': `Bearer ${creds.api_key}`,
637
- 'Content-Type': 'application/json',
638
- },
639
- body: JSON.stringify({
640
- version: version || undefined,
641
- model: version ? undefined : modelPath,
642
- input,
643
- }),
644
- }, { provider: 'replicate', action: 'run' });
645
- if (!response.ok) {
646
- const error = await response.json().catch(() => ({}));
647
- return createErrorResult('replicate', 'run', error.detail || 'Prediction failed', statusToErrorCode(response.status));
648
- }
649
- const prediction = await response.json();
650
- // Poll for completion (max 60 seconds)
651
- let result = prediction;
652
- const startTime = Date.now();
653
- while (result.status === 'starting' || result.status === 'processing') {
654
- if (Date.now() - startTime > 60000) {
655
- return {
656
- success: true,
657
- provider: 'replicate',
658
- action: 'run',
659
- data: {
660
- status: 'pending',
661
- prediction_id: result.id,
662
- message: 'Prediction still running. Use prediction_id to check status.',
663
- urls: result.urls
664
- }
665
- };
666
- }
667
- await sleep(1000);
668
- const pollResponse = await fetchWithRetry(result.urls?.get || `https://api.replicate.com/v1/predictions/${result.id}`, {
669
- headers: { 'Authorization': `Bearer ${creds.api_key}` },
670
- }, { provider: 'replicate', action: 'run_poll' });
671
- result = await pollResponse.json();
672
- }
673
- if (result.status === 'failed') {
674
- return createErrorResult('replicate', 'run', result.error || 'Prediction failed', ERROR_CODES.PROVIDER_ERROR);
675
- }
676
- return {
677
- success: true,
678
- provider: 'replicate',
679
- action: 'run',
680
- data: {
681
- status: result.status,
682
- output: result.output,
683
- model: modelPath,
684
- metrics: result.metrics
685
- }
686
- };
687
- },
688
- list_models: async (_params, creds) => {
689
- const response = await fetchWithRetry('https://api.replicate.com/v1/models', {
690
- headers: { 'Authorization': `Bearer ${creds.api_key}` },
691
- }, { provider: 'replicate', action: 'list_models' });
692
- if (!response.ok) {
693
- return createErrorResult('replicate', 'list_models', 'Failed to list models', statusToErrorCode(response.status));
694
- }
695
- const data = await response.json();
696
- return {
697
- success: true,
698
- provider: 'replicate',
699
- action: 'list_models',
700
- data: {
701
- models: data.results,
702
- message: 'Use model owner/name with run action. Popular: stability-ai/sdxl, meta/llama-2-70b-chat, openai/whisper'
703
- }
704
- };
705
- },
706
- },
707
- // Firecrawl - Web scraping and crawling
708
- firecrawl: {
709
- scrape: async (params, creds) => {
710
- const { url, formats = ['markdown'] } = params;
711
- if (!url) {
712
- return createErrorResult('firecrawl', 'scrape', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
713
- }
714
- const response = await fetchWithRetry('https://api.firecrawl.dev/v1/scrape', {
715
- method: 'POST',
716
- headers: {
717
- 'Authorization': `Bearer ${creds.api_key}`,
718
- 'Content-Type': 'application/json',
719
- },
720
- body: JSON.stringify({ url, formats }),
721
- }, { provider: 'firecrawl', action: 'scrape' });
722
- const data = await response.json();
723
- if (!response.ok || !data.success) {
724
- return createErrorResult('firecrawl', 'scrape', data.error || 'Scrape failed', statusToErrorCode(response.status));
725
- }
726
- return {
727
- success: true,
728
- provider: 'firecrawl',
729
- action: 'scrape',
730
- data: data.data,
731
- };
732
- },
733
- crawl: async (params, creds) => {
734
- const { url, limit = 10 } = params;
735
- if (!url) {
736
- return createErrorResult('firecrawl', 'crawl', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
737
- }
738
- const response = await fetchWithRetry('https://api.firecrawl.dev/v1/crawl', {
739
- method: 'POST',
740
- headers: {
741
- 'Authorization': `Bearer ${creds.api_key}`,
742
- 'Content-Type': 'application/json',
743
- },
744
- body: JSON.stringify({ url, limit }),
745
- }, { provider: 'firecrawl', action: 'crawl' });
746
- const data = await response.json();
747
- if (!response.ok || !data.success) {
748
- return createErrorResult('firecrawl', 'crawl', data.error || 'Crawl failed', statusToErrorCode(response.status));
749
- }
750
- return {
751
- success: true,
752
- provider: 'firecrawl',
753
- action: 'crawl',
754
- data: { id: data.id, status: 'started', message: 'Crawl job started. Poll status with crawl_status action.' },
755
- };
756
- },
757
- map: async (params, creds) => {
758
- const { url } = params;
759
- if (!url) {
760
- return createErrorResult('firecrawl', 'map', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
761
- }
762
- const response = await fetchWithRetry('https://api.firecrawl.dev/v1/map', {
763
- method: 'POST',
764
- headers: {
765
- 'Authorization': `Bearer ${creds.api_key}`,
766
- 'Content-Type': 'application/json',
767
- },
768
- body: JSON.stringify({ url }),
769
- }, { provider: 'firecrawl', action: 'map' });
770
- const data = await response.json();
771
- if (!response.ok || !data.success) {
772
- return createErrorResult('firecrawl', 'map', data.error || 'Map failed', statusToErrorCode(response.status));
773
- }
774
- return {
775
- success: true,
776
- provider: 'firecrawl',
777
- action: 'map',
778
- data: { links: data.links },
779
- };
780
- },
781
- },
782
- // GitHub - Code & Repos
783
- github: {
784
- search_repos: async (params, creds) => {
785
- const { query, sort = 'stars', limit = 10 } = params;
786
- if (!query) {
787
- return createErrorResult('github', 'search_repos', 'Missing required param: query', ERROR_CODES.INVALID_PARAMS);
788
- }
789
- const response = await fetchWithRetry(`https://api.github.com/search/repositories?q=${encodeURIComponent(query)}&sort=${sort}&per_page=${limit}`, {
790
- headers: {
791
- 'Authorization': `Bearer ${creds.token}`,
792
- 'Accept': 'application/vnd.github+json',
793
- 'User-Agent': 'APIClaw',
794
- },
795
- }, { provider: 'github', action: 'search_repos' });
796
- const data = await response.json();
797
- if (!response.ok) {
798
- return createErrorResult('github', 'search_repos', data.message || 'Search failed', statusToErrorCode(response.status));
799
- }
800
- const items = data.items || [];
801
- return {
802
- success: true,
803
- provider: 'github',
804
- action: 'search_repos',
805
- data: {
806
- total: data.total_count,
807
- repos: items.slice(0, limit).map(r => ({
808
- name: r.full_name,
809
- description: r.description,
810
- stars: r.stargazers_count,
811
- url: r.html_url,
812
- language: r.language,
813
- }))
814
- },
815
- };
816
- },
817
- get_repo: async (params, creds) => {
818
- const { owner, repo } = params;
819
- if (!owner || !repo) {
820
- return createErrorResult('github', 'get_repo', 'Missing required params: owner, repo', ERROR_CODES.INVALID_PARAMS);
821
- }
822
- const response = await fetchWithRetry(`https://api.github.com/repos/${owner}/${repo}`, {
823
- headers: {
824
- 'Authorization': `Bearer ${creds.token}`,
825
- 'Accept': 'application/vnd.github+json',
826
- 'User-Agent': 'APIClaw',
827
- },
828
- }, { provider: 'github', action: 'get_repo' });
829
- const data = await response.json();
830
- if (!response.ok) {
831
- return createErrorResult('github', 'get_repo', data.message || 'Get repo failed', statusToErrorCode(response.status));
832
- }
833
- return {
834
- success: true,
835
- provider: 'github',
836
- action: 'get_repo',
837
- data: {
838
- name: data.full_name,
839
- description: data.description,
840
- stars: data.stargazers_count,
841
- forks: data.forks_count,
842
- language: data.language,
843
- url: data.html_url,
844
- created: data.created_at,
845
- updated: data.updated_at,
846
- },
847
- };
848
- },
849
- list_issues: async (params, creds) => {
850
- const { owner, repo, state = 'open', limit = 10 } = params;
851
- if (!owner || !repo) {
852
- return createErrorResult('github', 'list_issues', 'Missing required params: owner, repo', ERROR_CODES.INVALID_PARAMS);
853
- }
854
- const response = await fetchWithRetry(`https://api.github.com/repos/${owner}/${repo}/issues?state=${state}&per_page=${limit}`, {
855
- headers: {
856
- 'Authorization': `Bearer ${creds.token}`,
857
- 'Accept': 'application/vnd.github+json',
858
- 'User-Agent': 'APIClaw',
859
- },
860
- }, { provider: 'github', action: 'list_issues' });
861
- const data = await response.json();
862
- if (!response.ok) {
863
- return createErrorResult('github', 'list_issues', 'List issues failed', statusToErrorCode(response.status));
864
- }
865
- return {
866
- success: true,
867
- provider: 'github',
868
- action: 'list_issues',
869
- data: {
870
- issues: data.map(i => ({
871
- number: i.number,
872
- title: i.title,
873
- state: i.state,
874
- user: i.user?.login,
875
- url: i.html_url,
876
- created: i.created_at,
877
- }))
878
- },
879
- };
880
- },
881
- create_issue: async (params, creds) => {
882
- const { owner, repo, title, body = '' } = params;
883
- if (!owner || !repo || !title) {
884
- return createErrorResult('github', 'create_issue', 'Missing required params: owner, repo, title', ERROR_CODES.INVALID_PARAMS);
885
- }
886
- const response = await fetchWithRetry(`https://api.github.com/repos/${owner}/${repo}/issues`, {
887
- method: 'POST',
888
- headers: {
889
- 'Authorization': `Bearer ${creds.token}`,
890
- 'Accept': 'application/vnd.github+json',
891
- 'User-Agent': 'APIClaw',
892
- 'Content-Type': 'application/json',
893
- },
894
- body: JSON.stringify({ title, body }),
895
- }, { provider: 'github', action: 'create_issue' });
896
- const data = await response.json();
897
- if (!response.ok) {
898
- return createErrorResult('github', 'create_issue', data.message || 'Create issue failed', statusToErrorCode(response.status));
899
- }
900
- return {
901
- success: true,
902
- provider: 'github',
903
- action: 'create_issue',
904
- data: {
905
- number: data.number,
906
- url: data.html_url,
907
- },
908
- };
909
- },
910
- get_file: async (params, creds) => {
911
- const { owner, repo, path } = params;
912
- if (!owner || !repo || !path) {
913
- return createErrorResult('github', 'get_file', 'Missing required params: owner, repo, path', ERROR_CODES.INVALID_PARAMS);
914
- }
915
- const response = await fetchWithRetry(`https://api.github.com/repos/${owner}/${repo}/contents/${path}`, {
916
- headers: {
917
- 'Authorization': `Bearer ${creds.token}`,
918
- 'Accept': 'application/vnd.github+json',
919
- 'User-Agent': 'APIClaw',
920
- },
921
- }, { provider: 'github', action: 'get_file' });
922
- const data = await response.json();
923
- if (!response.ok) {
924
- return createErrorResult('github', 'get_file', data.message || 'Get file failed', statusToErrorCode(response.status));
925
- }
926
- // Decode base64 content
927
- const content = data.content ? Buffer.from(data.content, 'base64').toString('utf-8') : null;
928
- return {
929
- success: true,
930
- provider: 'github',
931
- action: 'get_file',
932
- data: {
933
- name: data.name,
934
- path: data.path,
935
- size: data.size,
936
- content,
937
- },
938
- };
939
- },
940
- },
941
- // E2B - Code Sandbox for AI Agents
942
- // Uses @e2b/code-interpreter SDK
943
- e2b: {
944
- run_code: async (params, creds) => {
945
- const { code, language = 'python' } = params;
946
- if (!code) {
947
- return createErrorResult('e2b', 'run_code', 'Missing required param: code', ERROR_CODES.INVALID_PARAMS);
948
- }
949
- try {
950
- // Dynamic import to avoid issues if SDK not installed
951
- const { Sandbox } = await import('@e2b/code-interpreter');
952
- // Set API key via env (SDK reads from E2B_API_KEY)
953
- process.env.E2B_API_KEY = creds.api_key;
954
- const sandbox = await Sandbox.create();
955
- try {
956
- const execution = await sandbox.runCode(code);
957
- return {
958
- success: true,
959
- provider: 'e2b',
960
- action: 'run_code',
961
- data: {
962
- text: execution.text,
963
- logs: execution.logs,
964
- results: execution.results,
965
- },
966
- };
967
- }
968
- finally {
969
- await sandbox.kill().catch(() => { });
970
- }
971
- }
972
- catch (error) {
973
- return createErrorResult('e2b', 'run_code', error.message || 'Code execution failed', ERROR_CODES.PROVIDER_ERROR);
974
- }
975
- },
976
- run_shell: async (params, creds) => {
977
- const { command } = params;
978
- if (!command) {
979
- return createErrorResult('e2b', 'run_shell', 'Missing required param: command', ERROR_CODES.INVALID_PARAMS);
980
- }
981
- try {
982
- const { Sandbox } = await import('@e2b/code-interpreter');
983
- process.env.E2B_API_KEY = creds.api_key;
984
- const sandbox = await Sandbox.create();
985
- try {
986
- const result = await sandbox.commands.run(command);
987
- return {
988
- success: true,
989
- provider: 'e2b',
990
- action: 'run_shell',
991
- data: {
992
- stdout: result.stdout,
993
- stderr: result.stderr,
994
- exitCode: result.exitCode,
995
- },
996
- };
997
- }
998
- finally {
999
- await sandbox.kill().catch(() => { });
1000
- }
1001
- }
1002
- catch (error) {
1003
- return createErrorResult('e2b', 'run_shell', error.message || 'Shell execution failed', ERROR_CODES.PROVIDER_ERROR);
1004
- }
1005
- },
1006
- },
1007
- // Groq - Ultra-fast LLM inference
1008
- groq: {
1009
- chat: async (params, creds) => {
1010
- const { messages, model = 'llama3-8b-8192', max_tokens = 1024 } = params;
1011
- if (!messages || !Array.isArray(messages)) {
1012
- return createErrorResult('groq', 'chat', 'Missing required param: messages (array)', ERROR_CODES.INVALID_PARAMS);
1013
- }
1014
- const response = await fetchWithRetry('https://api.groq.com/openai/v1/chat/completions', {
1015
- method: 'POST',
1016
- headers: {
1017
- 'Authorization': `Bearer ${creds.api_key}`,
1018
- 'Content-Type': 'application/json',
1019
- },
1020
- body: JSON.stringify({ model, messages, max_tokens }),
1021
- }, { provider: 'groq', action: 'chat' });
1022
- const data = await response.json();
1023
- if (!response.ok) {
1024
- const err = data.error;
1025
- return createErrorResult('groq', 'chat', err?.message || 'Chat failed', statusToErrorCode(response.status));
1026
- }
1027
- const choices = data.choices;
1028
- const message = choices?.[0]?.message;
1029
- return {
1030
- success: true,
1031
- provider: 'groq',
1032
- action: 'chat',
1033
- data: {
1034
- content: message?.content,
1035
- model: data.model,
1036
- usage: data.usage,
1037
- },
1038
- };
1039
- },
1040
- },
1041
- // Deepgram - Speech-to-text transcription
1042
- deepgram: {
1043
- transcribe: async (params, creds) => {
1044
- const { url, model = 'nova-2', language = 'en' } = params;
1045
- if (!url) {
1046
- return createErrorResult('deepgram', 'transcribe', 'Missing required param: url (audio file URL)', ERROR_CODES.INVALID_PARAMS);
1047
- }
1048
- const response = await fetchWithRetry(`https://api.deepgram.com/v1/listen?model=${model}&language=${language}&smart_format=true`, {
1049
- method: 'POST',
1050
- headers: {
1051
- 'Authorization': `Token ${creds.api_key}`,
1052
- 'Content-Type': 'application/json',
1053
- },
1054
- body: JSON.stringify({ url }),
1055
- }, { provider: 'deepgram', action: 'transcribe' });
1056
- const data = await response.json();
1057
- if (!response.ok) {
1058
- return createErrorResult('deepgram', 'transcribe', data.err_msg || 'Transcription failed', statusToErrorCode(response.status));
1059
- }
1060
- const results = data.results;
1061
- const channels = results?.channels;
1062
- const alternatives = channels?.[0]?.alternatives;
1063
- const transcript = alternatives?.[0]?.transcript;
1064
- return {
1065
- success: true,
1066
- provider: 'deepgram',
1067
- action: 'transcribe',
1068
- data: {
1069
- transcript,
1070
- confidence: alternatives?.[0]?.confidence,
1071
- duration: data.metadata?.duration,
1072
- },
1073
- };
1074
- },
1075
- },
1076
- // Serper - Google Search API for AI
1077
- serper: {
1078
- search: async (params, creds) => {
1079
- const { query, num = 10, gl = 'us', hl = 'en' } = params;
1080
- if (!query) {
1081
- return createErrorResult('serper', 'search', 'Missing required param: query', ERROR_CODES.INVALID_PARAMS);
1082
- }
1083
- const response = await fetchWithRetry('https://google.serper.dev/search', {
1084
- method: 'POST',
1085
- headers: {
1086
- 'X-API-KEY': creds.api_key,
1087
- 'Content-Type': 'application/json',
1088
- },
1089
- body: JSON.stringify({ q: query, num, gl, hl }),
1090
- }, { provider: 'serper', action: 'search' });
1091
- const data = await response.json();
1092
- if (!response.ok) {
1093
- return createErrorResult('serper', 'search', data.message || 'Search failed', statusToErrorCode(response.status));
1094
- }
1095
- const organic = data.organic || [];
1096
- return {
1097
- success: true,
1098
- provider: 'serper',
1099
- action: 'search',
1100
- data: {
1101
- query,
1102
- results: organic.map(r => ({
1103
- title: r.title,
1104
- url: r.link,
1105
- snippet: r.snippet,
1106
- position: r.position,
1107
- })),
1108
- total: organic.length,
1109
- answerBox: data.answerBox,
1110
- knowledgeGraph: data.knowledgeGraph,
1111
- },
1112
- };
1113
- },
1114
- },
1115
- // Mistral - Open-weight LLMs
1116
- mistral: {
1117
- chat: async (params, creds) => {
1118
- const { messages, model = 'mistral-small-latest', max_tokens = 1024 } = params;
1119
- if (!messages || !Array.isArray(messages)) {
1120
- return createErrorResult('mistral', 'chat', 'Missing required param: messages (array)', ERROR_CODES.INVALID_PARAMS);
1121
- }
1122
- const response = await fetchWithRetry('https://api.mistral.ai/v1/chat/completions', {
1123
- method: 'POST',
1124
- headers: {
1125
- 'Authorization': `Bearer ${creds.api_key}`,
1126
- 'Content-Type': 'application/json',
1127
- },
1128
- body: JSON.stringify({ model, messages, max_tokens }),
1129
- }, { provider: 'mistral', action: 'chat' });
1130
- const data = await response.json();
1131
- if (!response.ok) {
1132
- const err = data.message;
1133
- return createErrorResult('mistral', 'chat', err || 'Chat failed', statusToErrorCode(response.status));
1134
- }
1135
- const choices = data.choices;
1136
- const message = choices?.[0]?.message;
1137
- return {
1138
- success: true,
1139
- provider: 'mistral',
1140
- action: 'chat',
1141
- data: {
1142
- content: message?.content,
1143
- model: data.model,
1144
- usage: data.usage,
1145
- },
1146
- };
1147
- },
1148
- embed: async (params, creds) => {
1149
- const { input, model = 'mistral-embed' } = params;
1150
- if (!input) {
1151
- return createErrorResult('mistral', 'embed', 'Missing required param: input (string or array)', ERROR_CODES.INVALID_PARAMS);
1152
- }
1153
- const inputs = Array.isArray(input) ? input : [input];
1154
- const response = await fetchWithRetry('https://api.mistral.ai/v1/embeddings', {
1155
- method: 'POST',
1156
- headers: {
1157
- 'Authorization': `Bearer ${creds.api_key}`,
1158
- 'Content-Type': 'application/json',
1159
- },
1160
- body: JSON.stringify({ model, input: inputs }),
1161
- }, { provider: 'mistral', action: 'embed' });
1162
- const data = await response.json();
1163
- if (!response.ok) {
1164
- return createErrorResult('mistral', 'embed', data.message || 'Embedding failed', statusToErrorCode(response.status));
1165
- }
1166
- const embedData = data.data;
1167
- return {
1168
- success: true,
1169
- provider: 'mistral',
1170
- action: 'embed',
1171
- data: {
1172
- embeddings: embedData?.map(d => d.embedding),
1173
- model: data.model,
1174
- usage: data.usage,
1175
- },
1176
- };
1177
- },
1178
- },
1179
- // Cohere - Enterprise NLP and embeddings
1180
- cohere: {
1181
- chat: async (params, creds) => {
1182
- const { message, model = 'command-r', max_tokens = 1024, preamble } = params;
1183
- if (!message) {
1184
- return createErrorResult('cohere', 'chat', 'Missing required param: message', ERROR_CODES.INVALID_PARAMS);
1185
- }
1186
- const body = { model, message, max_tokens };
1187
- if (preamble)
1188
- body.preamble = preamble;
1189
- const response = await fetchWithRetry('https://api.cohere.com/v1/chat', {
1190
- method: 'POST',
1191
- headers: {
1192
- 'Authorization': `Bearer ${creds.api_key}`,
1193
- 'Content-Type': 'application/json',
1194
- },
1195
- body: JSON.stringify(body),
1196
- }, { provider: 'cohere', action: 'chat' });
1197
- const data = await response.json();
1198
- if (!response.ok) {
1199
- return createErrorResult('cohere', 'chat', data.message || 'Chat failed', statusToErrorCode(response.status));
1200
- }
1201
- return {
1202
- success: true,
1203
- provider: 'cohere',
1204
- action: 'chat',
1205
- data: {
1206
- content: data.text,
1207
- generation_id: data.generation_id,
1208
- usage: data.meta,
1209
- },
1210
- };
1211
- },
1212
- embed: async (params, creds) => {
1213
- const { texts, model = 'embed-english-v3.0', input_type = 'search_document' } = params;
1214
- if (!texts || !Array.isArray(texts)) {
1215
- return createErrorResult('cohere', 'embed', 'Missing required param: texts (array of strings)', ERROR_CODES.INVALID_PARAMS);
1216
- }
1217
- const response = await fetchWithRetry('https://api.cohere.com/v1/embed', {
1218
- method: 'POST',
1219
- headers: {
1220
- 'Authorization': `Bearer ${creds.api_key}`,
1221
- 'Content-Type': 'application/json',
1222
- },
1223
- body: JSON.stringify({ model, texts, input_type }),
1224
- }, { provider: 'cohere', action: 'embed' });
1225
- const data = await response.json();
1226
- if (!response.ok) {
1227
- return createErrorResult('cohere', 'embed', data.message || 'Embedding failed', statusToErrorCode(response.status));
1228
- }
1229
- return {
1230
- success: true,
1231
- provider: 'cohere',
1232
- action: 'embed',
1233
- data: {
1234
- embeddings: data.embeddings,
1235
- model: data.model,
1236
- },
1237
- };
1238
- },
1239
- },
1240
- // Together AI - Open-source model inference
1241
- together: {
1242
- chat: async (params, creds) => {
1243
- const { messages, model = 'meta-llama/Llama-3-8b-chat-hf', max_tokens = 1024 } = params;
1244
- if (!messages || !Array.isArray(messages)) {
1245
- return createErrorResult('together', 'chat', 'Missing required param: messages (array)', ERROR_CODES.INVALID_PARAMS);
1246
- }
1247
- const response = await fetchWithRetry('https://api.together.xyz/v1/chat/completions', {
1248
- method: 'POST',
1249
- headers: {
1250
- 'Authorization': `Bearer ${creds.api_key}`,
1251
- 'Content-Type': 'application/json',
1252
- },
1253
- body: JSON.stringify({ model, messages, max_tokens }),
1254
- }, { provider: 'together', action: 'chat' });
1255
- const data = await response.json();
1256
- if (!response.ok) {
1257
- const err = data.error;
1258
- return createErrorResult('together', 'chat', err?.message || 'Chat failed', statusToErrorCode(response.status));
1259
- }
1260
- const choices = data.choices;
1261
- const message = choices?.[0]?.message;
1262
- return {
1263
- success: true,
1264
- provider: 'together',
1265
- action: 'chat',
1266
- data: {
1267
- content: message?.content,
1268
- model: data.model,
1269
- usage: data.usage,
1270
- },
1271
- };
1272
- },
1273
- },
1274
- // Stability AI - Image generation
1275
- stability: {
1276
- generate_image: async (params, creds) => {
1277
- const { prompt, model = 'stable-diffusion-xl-1024-v1-0', width = 1024, height = 1024, steps = 30 } = params;
1278
- if (!prompt) {
1279
- return createErrorResult('stability', 'generate_image', 'Missing required param: prompt', ERROR_CODES.INVALID_PARAMS);
1280
- }
1281
- const response = await fetchWithRetry(`https://api.stability.ai/v1/generation/${model}/text-to-image`, {
1282
- method: 'POST',
1283
- headers: {
1284
- 'Authorization': `Bearer ${creds.api_key}`,
1285
- 'Content-Type': 'application/json',
1286
- 'Accept': 'application/json',
1287
- },
1288
- body: JSON.stringify({
1289
- text_prompts: [{ text: prompt, weight: 1 }],
1290
- width,
1291
- height,
1292
- steps,
1293
- samples: 1,
1294
- }),
1295
- }, { provider: 'stability', action: 'generate_image' });
1296
- const data = await response.json();
1297
- if (!response.ok) {
1298
- return createErrorResult('stability', 'generate_image', data.message || 'Image generation failed', statusToErrorCode(response.status));
1299
- }
1300
- const artifacts = data.artifacts;
1301
- const image = artifacts?.[0];
1302
- return {
1303
- success: true,
1304
- provider: 'stability',
1305
- action: 'generate_image',
1306
- data: {
1307
- image_base64: image?.base64,
1308
- finish_reason: image?.finishReason,
1309
- seed: image?.seed,
1310
- },
1311
- };
1312
- },
1313
- },
1314
- // AssemblyAI - Audio transcription and intelligence
1315
- assemblyai: {
1316
- transcribe: async (params, creds) => {
1317
- const { audio_url, language_code = 'en', speaker_labels = false, sentiment_analysis = false } = params;
1318
- if (!audio_url) {
1319
- return createErrorResult('assemblyai', 'transcribe', 'Missing required param: audio_url', ERROR_CODES.INVALID_PARAMS);
1320
- }
1321
- // Submit transcription job
1322
- const submitResponse = await fetchWithRetry('https://api.assemblyai.com/v2/transcript', {
1323
- method: 'POST',
1324
- headers: {
1325
- 'Authorization': creds.api_key,
1326
- 'Content-Type': 'application/json',
1327
- },
1328
- body: JSON.stringify({ audio_url, language_code, speaker_labels, sentiment_analysis }),
1329
- }, { provider: 'assemblyai', action: 'transcribe' });
1330
- const submitData = await submitResponse.json();
1331
- if (!submitResponse.ok) {
1332
- return createErrorResult('assemblyai', 'transcribe', submitData.error || 'Submit failed', statusToErrorCode(submitResponse.status));
1333
- }
1334
- const transcriptId = submitData.id;
1335
- // Poll until complete (max 120 seconds)
1336
- const startTime = Date.now();
1337
- while (Date.now() - startTime < 120000) {
1338
- await sleep(3000);
1339
- const pollResponse = await fetchWithRetry(`https://api.assemblyai.com/v2/transcript/${transcriptId}`, {
1340
- headers: { 'Authorization': creds.api_key },
1341
- }, { provider: 'assemblyai', action: 'transcribe_poll' });
1342
- const pollData = await pollResponse.json();
1343
- if (pollData.status === 'completed') {
1344
- return {
1345
- success: true,
1346
- provider: 'assemblyai',
1347
- action: 'transcribe',
1348
- data: {
1349
- transcript: pollData.text,
1350
- words: pollData.words,
1351
- utterances: pollData.utterances,
1352
- sentiment_analysis_results: pollData.sentiment_analysis_results,
1353
- audio_duration: pollData.audio_duration,
1354
- },
1355
- };
1356
- }
1357
- if (pollData.status === 'error') {
1358
- return createErrorResult('assemblyai', 'transcribe', pollData.error || 'Transcription failed', ERROR_CODES.PROVIDER_ERROR);
1359
- }
1360
- }
1361
- return {
1362
- success: true,
1363
- provider: 'assemblyai',
1364
- action: 'transcribe',
1365
- data: {
1366
- status: 'processing',
1367
- transcript_id: transcriptId,
1368
- message: 'Transcription still processing. Use transcript_id to poll manually.',
1369
- },
1370
- };
1371
- },
1372
- },
1373
- // APILayer - 14 APIs via one provider
1374
- apilayer: {
1375
- // Helper to pick the right key per action
1376
- exchange_rates: async (params, creds) => {
1377
- const key = creds.APILAYER_EXCHANGERATE_KEY || creds.api_key;
1378
- const { base = 'USD', symbols, date } = params;
1379
- const endpoint = date ? 'historical' : 'latest';
1380
- const url = new URL(`https://api.apilayer.com/exchangerates_data/${endpoint}`);
1381
- url.searchParams.set('base', base);
1382
- if (symbols)
1383
- url.searchParams.set('symbols', symbols);
1384
- if (date)
1385
- url.searchParams.set('date', date);
1386
- const response = await fetchWithRetry(url.toString(), {
1387
- headers: { 'apikey': key },
1388
- }, { provider: 'apilayer', action: 'exchange_rates' });
1389
- const data = await response.json();
1390
- if (!response.ok)
1391
- return createErrorResult('apilayer', 'exchange_rates', data.message || 'Request failed', statusToErrorCode(response.status));
1392
- return { success: true, provider: 'apilayer', action: 'exchange_rates', data };
1393
- },
1394
- market_data: async (params, creds) => {
1395
- const key = creds.APILAYER_MARKETSTACK_KEY || creds.api_key;
1396
- const { symbols, date_from, date_to, limit = 10 } = params;
1397
- if (!symbols)
1398
- return createErrorResult('apilayer', 'market_data', 'Missing required param: symbols', ERROR_CODES.INVALID_PARAMS);
1399
- const url = new URL('http://api.marketstack.com/v1/eod');
1400
- url.searchParams.set('access_key', key);
1401
- url.searchParams.set('symbols', symbols);
1402
- url.searchParams.set('limit', limit.toString());
1403
- if (date_from)
1404
- url.searchParams.set('date_from', date_from);
1405
- if (date_to)
1406
- url.searchParams.set('date_to', date_to);
1407
- const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'market_data' });
1408
- const data = await response.json();
1409
- if (!response.ok)
1410
- return createErrorResult('apilayer', 'market_data', data.error?.message || 'Request failed', statusToErrorCode(response.status));
1411
- return { success: true, provider: 'apilayer', action: 'market_data', data };
1412
- },
1413
- aviation: async (params, creds) => {
1414
- const key = creds.APILAYER_AVIATIONSTACK_KEY || creds.api_key;
1415
- const { flight_iata, dep_iata, arr_iata, airline_iata } = params;
1416
- const url = new URL('http://api.aviationstack.com/v1/flights');
1417
- url.searchParams.set('access_key', key);
1418
- if (flight_iata)
1419
- url.searchParams.set('flight_iata', flight_iata);
1420
- if (dep_iata)
1421
- url.searchParams.set('dep_iata', dep_iata);
1422
- if (arr_iata)
1423
- url.searchParams.set('arr_iata', arr_iata);
1424
- if (airline_iata)
1425
- url.searchParams.set('airline_iata', airline_iata);
1426
- const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'aviation' });
1427
- const data = await response.json();
1428
- if (!response.ok)
1429
- return createErrorResult('apilayer', 'aviation', 'Request failed', statusToErrorCode(response.status));
1430
- return { success: true, provider: 'apilayer', action: 'aviation', data };
1431
- },
1432
- pdf_generate: async (params, creds) => {
1433
- const key = creds.APILAYER_PDFLAYER_KEY || creds.api_key;
1434
- const { document_url, document_html, page_size = 'A4' } = params;
1435
- if (!document_url && !document_html)
1436
- return createErrorResult('apilayer', 'pdf_generate', 'Missing: document_url or document_html', ERROR_CODES.INVALID_PARAMS);
1437
- const url = new URL('https://api.pdflayer.com/api');
1438
- url.searchParams.set('page_size', page_size);
1439
- if (document_url)
1440
- url.searchParams.set('document_url', document_url);
1441
- if (document_html)
1442
- url.searchParams.set('document_html', document_html);
1443
- const response = await fetchWithRetry(url.toString(), { method: 'POST', headers: { 'apikey': key } }, { provider: 'apilayer', action: 'pdf_generate' });
1444
- const contentType = response.headers.get('content-type') || '';
1445
- if (contentType.includes('application/pdf')) {
1446
- return { success: true, provider: 'apilayer', action: 'pdf_generate', data: { message: 'PDF generated', content_type: 'application/pdf', size: response.headers.get('content-length') } };
1447
- }
1448
- const data = await response.json();
1449
- if (!response.ok)
1450
- return createErrorResult('apilayer', 'pdf_generate', data.error?.info || 'Request failed', statusToErrorCode(response.status));
1451
- return { success: true, provider: 'apilayer', action: 'pdf_generate', data };
1452
- },
1453
- screenshot: async (params, creds) => {
1454
- const key = creds.APILAYER_SCREENSHOTLAYER_KEY || creds.api_key;
1455
- const { url: targetUrl, viewport = '1440x900', fullpage = 0 } = params;
1456
- if (!targetUrl)
1457
- return createErrorResult('apilayer', 'screenshot', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1458
- const url = new URL('https://api.screenshotlayer.com/api/capture');
1459
- url.searchParams.set('access_key', key);
1460
- url.searchParams.set('url', targetUrl);
1461
- url.searchParams.set('viewport', viewport);
1462
- url.searchParams.set('fullpage', fullpage.toString());
1463
- const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'screenshot' });
1464
- const contentType = response.headers.get('content-type') || '';
1465
- if (contentType.includes('image/')) {
1466
- return { success: true, provider: 'apilayer', action: 'screenshot', data: { message: 'Screenshot captured', content_type: contentType, url: url.toString() } };
1467
- }
1468
- const data = await response.json();
1469
- if (!response.ok)
1470
- return createErrorResult('apilayer', 'screenshot', 'Request failed', statusToErrorCode(response.status));
1471
- return { success: true, provider: 'apilayer', action: 'screenshot', data };
1472
- },
1473
- verify_email: async (params, creds) => {
1474
- const key = creds.APILAYER_EMAILVERIFY_KEY || creds.api_key;
1475
- const { email } = params;
1476
- if (!email)
1477
- return createErrorResult('apilayer', 'verify_email', 'Missing required param: email', ERROR_CODES.INVALID_PARAMS);
1478
- const url = new URL('https://api.apilayer.com/email_verification/check');
1479
- url.searchParams.set('email', email);
1480
- const response = await fetchWithRetry(url.toString(), {
1481
- headers: { 'apikey': key },
1482
- }, { provider: 'apilayer', action: 'verify_email' });
1483
- const data = await response.json();
1484
- if (!response.ok)
1485
- return createErrorResult('apilayer', 'verify_email', 'Request failed', statusToErrorCode(response.status));
1486
- return { success: true, provider: 'apilayer', action: 'verify_email', data };
1487
- },
1488
- verify_number: async (params, creds) => {
1489
- const key = creds.APILAYER_NUMVERIFY_KEY || creds.api_key;
1490
- const { number } = params;
1491
- if (!number)
1492
- return createErrorResult('apilayer', 'verify_number', 'Missing required param: number', ERROR_CODES.INVALID_PARAMS);
1493
- const url = new URL('https://api.apilayer.com/number_verification/validate');
1494
- url.searchParams.set('number', number);
1495
- const response = await fetchWithRetry(url.toString(), {
1496
- headers: { 'apikey': key },
1497
- }, { provider: 'apilayer', action: 'verify_number' });
1498
- const data = await response.json();
1499
- if (!response.ok)
1500
- return createErrorResult('apilayer', 'verify_number', 'Request failed', statusToErrorCode(response.status));
1501
- return { success: true, provider: 'apilayer', action: 'verify_number', data };
1502
- },
1503
- vat_check: async (params, creds) => {
1504
- const key = creds.APILAYER_VATLAYER_KEY || creds.api_key;
1505
- const { vat_number } = params;
1506
- if (!vat_number)
1507
- return createErrorResult('apilayer', 'vat_check', 'Missing required param: vat_number', ERROR_CODES.INVALID_PARAMS);
1508
- const url = new URL('http://apilayer.net/api/validate');
1509
- url.searchParams.set('access_key', key);
1510
- url.searchParams.set('vat_number', vat_number);
1511
- const response = await fetchWithRetry(url.toString(), {}, { provider: 'apilayer', action: 'vat_check' });
1512
- const data = await response.json();
1513
- if (!response.ok)
1514
- return createErrorResult('apilayer', 'vat_check', 'Request failed', statusToErrorCode(response.status));
1515
- return { success: true, provider: 'apilayer', action: 'vat_check', data };
1516
- },
1517
- world_news: async (params, creds) => {
1518
- const key = creds.APILAYER_WORLDNEWS_KEY || creds.api_key;
1519
- const { url: newsUrl, analyze = true } = params;
1520
- if (!newsUrl)
1521
- return createErrorResult('apilayer', 'world_news', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1522
- const url = new URL('https://api.apilayer.com/world_news/extract-news');
1523
- url.searchParams.set('url', newsUrl);
1524
- url.searchParams.set('analyze', analyze ? 'true' : 'false');
1525
- const response = await fetchWithRetry(url.toString(), {
1526
- headers: { 'apikey': key },
1527
- }, { provider: 'apilayer', action: 'world_news' });
1528
- const data = await response.json();
1529
- if (!response.ok)
1530
- return createErrorResult('apilayer', 'world_news', 'Request failed', statusToErrorCode(response.status));
1531
- return { success: true, provider: 'apilayer', action: 'world_news', data };
1532
- },
1533
- finance_news: async (params, creds) => {
1534
- const key = creds.APILAYER_FINANCENEWS_KEY || creds.api_key;
1535
- const { tickers, text, number = 5 } = params;
1536
- const url = new URL('https://api.apilayer.com/financelayer/news');
1537
- if (tickers)
1538
- url.searchParams.set('tickers', tickers);
1539
- if (text)
1540
- url.searchParams.set('keywords', text);
1541
- url.searchParams.set('limit', number.toString());
1542
- const response = await fetchWithRetry(url.toString(), {
1543
- headers: { 'apikey': key },
1544
- }, { provider: 'apilayer', action: 'finance_news' });
1545
- const data = await response.json();
1546
- if (!response.ok)
1547
- return createErrorResult('apilayer', 'finance_news', 'Request failed', statusToErrorCode(response.status));
1548
- return { success: true, provider: 'apilayer', action: 'finance_news', data };
1549
- },
1550
- scrape: async (params, creds) => {
1551
- const key = creds.APILAYER_SCRAPER_KEY || creds.api_key;
1552
- const { url: targetUrl } = params;
1553
- if (!targetUrl)
1554
- return createErrorResult('apilayer', 'scrape', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1555
- const url = new URL('https://api.apilayer.com/adv_scraper/scraper');
1556
- url.searchParams.set('url', targetUrl);
1557
- const response = await fetchWithRetry(url.toString(), {
1558
- headers: { 'apikey': key },
1559
- }, { provider: 'apilayer', action: 'scrape' });
1560
- const data = await response.json();
1561
- if (!response.ok)
1562
- return createErrorResult('apilayer', 'scrape', 'Request failed', statusToErrorCode(response.status));
1563
- return { success: true, provider: 'apilayer', action: 'scrape', data };
1564
- },
1565
- image_crop: async (params, creds) => {
1566
- const key = creds.APILAYER_IMAGECROP_KEY || creds.api_key;
1567
- const { url: imageUrl, width, height } = params;
1568
- if (!imageUrl)
1569
- return createErrorResult('apilayer', 'image_crop', 'Missing required param: url', ERROR_CODES.INVALID_PARAMS);
1570
- // APILayer smart_crop expects POST with form data
1571
- const formData = new URLSearchParams();
1572
- formData.set('url', imageUrl);
1573
- if (width)
1574
- formData.set('width', width.toString());
1575
- if (height)
1576
- formData.set('height', height.toString());
1577
- const response = await fetchWithRetry('https://api.apilayer.com/smart_crop/url', {
1578
- method: 'POST',
1579
- headers: {
1580
- 'apikey': key,
1581
- 'Content-Type': 'application/x-www-form-urlencoded',
1582
- },
1583
- body: formData.toString(),
1584
- }, { provider: 'apilayer', action: 'image_crop' });
1585
- const contentType = response.headers.get('content-type') || '';
1586
- if (contentType.includes('image/')) {
1587
- return { success: true, provider: 'apilayer', action: 'image_crop', data: { message: 'Image cropped', content_type: contentType } };
1588
- }
1589
- const data = await response.json();
1590
- if (!response.ok)
1591
- return createErrorResult('apilayer', 'image_crop', 'Request failed', statusToErrorCode(response.status));
1592
- return { success: true, provider: 'apilayer', action: 'image_crop', data };
1593
- },
1594
- skills: async (params, creds) => {
1595
- // Skills API is on PromptAPI domain, uses master key
1596
- const key = creds.APILAYER_EXCHANGERATE_KEY || creds.api_key;
1597
- const { q } = params;
1598
- if (!q)
1599
- return createErrorResult('apilayer', 'skills', 'Missing required param: q', ERROR_CODES.INVALID_PARAMS);
1600
- const url = new URL('https://api.promptapi.com/skills');
1601
- url.searchParams.set('q', q);
1602
- if (params.count)
1603
- url.searchParams.set('count', String(params.count));
1604
- const response = await fetchWithRetry(url.toString(), {
1605
- headers: { 'apikey': key },
1606
- }, { provider: 'apilayer', action: 'skills' });
1607
- const data = await response.json();
1608
- if (!response.ok)
1609
- return createErrorResult('apilayer', 'skills', 'Request failed', statusToErrorCode(response.status));
1610
- return { success: true, provider: 'apilayer', action: 'skills', data };
1611
- },
1612
- form_submit: async (params, creds) => {
1613
- const key = creds.APILAYER_FORMAPI_KEY || creds.api_key;
1614
- const { endpoint, data: formData } = params;
1615
- if (!endpoint)
1616
- return createErrorResult('apilayer', 'form_submit', 'Missing required param: endpoint', ERROR_CODES.INVALID_PARAMS);
1617
- const response = await fetchWithRetry(`https://api.apilayer.com/form_api/${endpoint}`, {
1618
- method: 'POST',
1619
- headers: {
1620
- 'apikey': key,
1621
- 'Content-Type': 'application/json',
1622
- },
1623
- body: JSON.stringify(formData || {}),
1624
- }, { provider: 'apilayer', action: 'form_submit' });
1625
- const data = await response.json();
1626
- if (!response.ok)
1627
- return createErrorResult('apilayer', 'form_submit', 'Request failed', statusToErrorCode(response.status));
1628
- return { success: true, provider: 'apilayer', action: 'form_submit', data };
1629
- },
1630
- },
1631
- };
1632
- // Get available actions for a provider (static handlers only)
1633
- export function getProviderActions(providerId) {
1634
- return Object.keys(handlers[providerId] || {});
1635
- }
1636
- // Get available actions for a provider (includes dynamic providers)
1637
- export async function getProviderActionsAsync(providerId) {
1638
- // First check static handlers
1639
- const staticActions = Object.keys(handlers[providerId] || {});
1640
- if (staticActions.length > 0) {
1641
- return staticActions;
1642
- }
1643
- // Then check dynamic providers
1644
- return listDynamicActions(providerId);
1645
- }
1646
- // Get all connected providers with their actions (static handlers only)
1647
- export function getConnectedProviders() {
1648
- return Object.entries(handlers).map(([provider, actions]) => ({
1649
- provider,
1650
- actions: Object.keys(actions),
1651
- }));
1652
- }
1653
- // Execute an API call
1654
- export async function executeAPICall(providerId, action, params, userId, customerKey) {
1655
- // Check for dynamic (self-service) provider config first
1656
- if (userId) {
1657
- const isDynamic = await hasDynamicConfig(providerId);
1658
- if (isDynamic) {
1659
- const dynamicResult = await executeDynamicAction(providerId, action, params, userId, customerKey);
1660
- return normalizeResponse(dynamicResult);
1661
- }
1662
- }
1663
- // Fall back to hardcoded handlers
1664
- // Check if provider exists
1665
- const providerHandlers = handlers[providerId];
1666
- if (!providerHandlers) {
1667
- // Check if it might be a dynamic provider without userId
1668
- const dynamicActions = await listDynamicActions(providerId);
1669
- if (dynamicActions.length > 0) {
1670
- return createErrorResult(providerId, action, `Provider '${providerId}' requires userId for dynamic execution. Available actions: ${dynamicActions.join(', ')}`, ERROR_CODES.INVALID_PARAMS);
1671
- }
1672
- return createErrorResult(providerId, action, `Provider '${providerId}' not connected. Available: ${Object.keys(handlers).join(', ')}`, ERROR_CODES.UNKNOWN_PROVIDER);
1673
- }
1674
- // Check if action exists
1675
- const handler = providerHandlers[action];
1676
- if (!handler) {
1677
- return createErrorResult(providerId, action, `Action '${action}' not available for ${providerId}. Available: ${Object.keys(providerHandlers).join(', ')}`, ERROR_CODES.UNKNOWN_ACTION);
1678
- }
1679
- // Providers that don't require credentials (free/open APIs)
1680
- const NO_CREDS_PROVIDERS = ['coingecko'];
1681
- // Get credentials - customer key takes priority, then local secrets, then proxy
1682
- // Set both apiKey and token so it works with different handler patterns (most use apiKey, GitHub uses token)
1683
- let creds = customerKey ? { apiKey: customerKey, api_key: customerKey, token: customerKey, apiSecret: '' } : getCredentials(providerId);
1684
- const usingCustomerKey = !!customerKey;
1685
- // For providers that don't need credentials, use empty creds
1686
- if (!creds && NO_CREDS_PROVIDERS.includes(providerId)) {
1687
- creds = { apiKey: '', api_key: '', token: '', apiSecret: '' };
1688
- }
1689
- if (!creds) {
1690
- // Try proxy for supported providers
1691
- if (PROXY_PROVIDERS.includes(providerId)) {
1692
- try {
1693
- const proxyResult = await callProxy(providerId, { action, ...params });
1694
- return normalizeResponse({
1695
- success: true,
1696
- provider: providerId,
1697
- action,
1698
- data: proxyResult,
1699
- });
1700
- }
1701
- catch (e) {
1702
- return createErrorResult(providerId, action, e.message || 'Proxy call failed', ERROR_CODES.PROVIDER_ERROR);
1703
- }
1704
- }
1705
- return createErrorResult(providerId, action, `No credentials configured for ${providerId}. Set up ~/.secrets/${providerId}.env`, ERROR_CODES.NO_CREDENTIALS);
1706
- }
1707
- // Execute and normalize response
1708
- try {
1709
- const result = await handler(params, creds);
1710
- return normalizeResponse(result);
1711
- }
1712
- catch (error) {
1713
- // Check if it's a network/timeout error
1714
- const errorMessage = error.message || 'Unknown error';
1715
- let errorCode = ERROR_CODES.PROVIDER_ERROR;
1716
- if (errorMessage.includes('Max retries exceeded')) {
1717
- errorCode = ERROR_CODES.MAX_RETRIES_EXCEEDED;
1718
- }
1719
- else if (errorMessage.includes('timeout') || errorMessage.includes('ETIMEDOUT')) {
1720
- errorCode = ERROR_CODES.TIMEOUT;
1721
- }
1722
- else if (errorMessage.includes('ECONNREFUSED') || errorMessage.includes('ENOTFOUND') || errorMessage.includes('fetch')) {
1723
- errorCode = ERROR_CODES.NETWORK_ERROR;
1724
- }
1725
- return createErrorResult(providerId, action, errorMessage, errorCode);
1726
- }
1727
- }