@nordsym/apiclaw 1.7.2 → 1.7.4

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 (230) hide show
  1. package/convex/_generated/api.d.ts +115 -0
  2. package/convex/_generated/api.js +23 -0
  3. package/convex/_generated/dataModel.d.ts +60 -0
  4. package/convex/_generated/server.d.ts +143 -0
  5. package/convex/_generated/server.js +93 -0
  6. package/convex/adminActivate.d.ts +3 -0
  7. package/convex/adminActivate.d.ts.map +1 -0
  8. package/convex/adminActivate.js +47 -0
  9. package/convex/adminActivate.js.map +1 -0
  10. package/convex/adminActivate.ts +54 -0
  11. package/convex/adminStats.d.ts +3 -0
  12. package/convex/adminStats.d.ts.map +1 -0
  13. package/convex/adminStats.js +42 -0
  14. package/convex/adminStats.js.map +1 -0
  15. package/convex/adminStats.ts +44 -0
  16. package/convex/agents.d.ts +76 -0
  17. package/convex/agents.d.ts.map +1 -0
  18. package/convex/agents.js +699 -0
  19. package/convex/agents.js.map +1 -0
  20. package/convex/agents.ts +814 -0
  21. package/convex/analytics.d.ts +5 -0
  22. package/convex/analytics.d.ts.map +1 -0
  23. package/convex/analytics.js +166 -0
  24. package/convex/analytics.js.map +1 -0
  25. package/convex/analytics.ts +186 -0
  26. package/convex/billing.d.ts +88 -0
  27. package/convex/billing.d.ts.map +1 -0
  28. package/convex/billing.js +655 -0
  29. package/convex/billing.js.map +1 -0
  30. package/convex/billing.ts +791 -0
  31. package/convex/capabilities.d.ts +9 -0
  32. package/convex/capabilities.d.ts.map +1 -0
  33. package/convex/capabilities.js +145 -0
  34. package/convex/capabilities.js.map +1 -0
  35. package/convex/capabilities.ts +157 -0
  36. package/convex/chains.d.ts +68 -0
  37. package/convex/chains.d.ts.map +1 -0
  38. package/convex/chains.js +1105 -0
  39. package/convex/chains.js.map +1 -0
  40. package/convex/chains.ts +1318 -0
  41. package/convex/credits.d.ts +25 -0
  42. package/convex/credits.d.ts.map +1 -0
  43. package/convex/credits.js +186 -0
  44. package/convex/credits.js.map +1 -0
  45. package/convex/credits.ts +211 -0
  46. package/convex/crons.d.ts +3 -0
  47. package/convex/crons.d.ts.map +1 -0
  48. package/convex/crons.js +17 -0
  49. package/convex/crons.js.map +1 -0
  50. package/convex/crons.ts +28 -0
  51. package/convex/directCall.d.ts +72 -0
  52. package/convex/directCall.d.ts.map +1 -0
  53. package/convex/directCall.js +627 -0
  54. package/convex/directCall.js.map +1 -0
  55. package/convex/directCall.ts +678 -0
  56. package/convex/earnProgress.d.ts +58 -0
  57. package/convex/earnProgress.d.ts.map +1 -0
  58. package/convex/earnProgress.js +649 -0
  59. package/convex/earnProgress.js.map +1 -0
  60. package/convex/earnProgress.ts +753 -0
  61. package/convex/email.d.ts +14 -0
  62. package/convex/email.d.ts.map +1 -0
  63. package/convex/email.js +300 -0
  64. package/convex/email.js.map +1 -0
  65. package/convex/email.ts +329 -0
  66. package/convex/feedback.d.ts +7 -0
  67. package/convex/feedback.d.ts.map +1 -0
  68. package/convex/feedback.js +227 -0
  69. package/convex/feedback.js.map +1 -0
  70. package/convex/feedback.ts +265 -0
  71. package/convex/http.d.ts +3 -0
  72. package/convex/http.d.ts.map +1 -0
  73. package/convex/http.js +1405 -0
  74. package/convex/http.js.map +1 -0
  75. package/convex/http.ts +1577 -0
  76. package/convex/inbound.d.ts +2 -0
  77. package/convex/inbound.d.ts.map +1 -0
  78. package/convex/inbound.js +32 -0
  79. package/convex/inbound.js.map +1 -0
  80. package/convex/inbound.ts +32 -0
  81. package/convex/logs.d.ts +38 -0
  82. package/convex/logs.d.ts.map +1 -0
  83. package/convex/logs.js +487 -0
  84. package/convex/logs.js.map +1 -0
  85. package/convex/logs.ts +550 -0
  86. package/convex/mou.d.ts +6 -0
  87. package/convex/mou.d.ts.map +1 -0
  88. package/convex/mou.js +82 -0
  89. package/convex/mou.js.map +1 -0
  90. package/convex/mou.ts +91 -0
  91. package/convex/providerKeys.d.ts +31 -0
  92. package/convex/providerKeys.d.ts.map +1 -0
  93. package/convex/providerKeys.js +257 -0
  94. package/convex/providerKeys.js.map +1 -0
  95. package/convex/providerKeys.ts +289 -0
  96. package/convex/providers.d.ts +32 -0
  97. package/convex/providers.d.ts.map +1 -0
  98. package/convex/providers.js +814 -0
  99. package/convex/providers.js.map +1 -0
  100. package/convex/providers.ts +909 -0
  101. package/convex/purchases.d.ts +7 -0
  102. package/convex/purchases.d.ts.map +1 -0
  103. package/convex/purchases.js +157 -0
  104. package/convex/purchases.js.map +1 -0
  105. package/convex/purchases.ts +183 -0
  106. package/convex/ratelimit.d.ts +4 -0
  107. package/convex/ratelimit.d.ts.map +1 -0
  108. package/convex/ratelimit.js +91 -0
  109. package/convex/ratelimit.js.map +1 -0
  110. package/convex/ratelimit.ts +104 -0
  111. package/convex/schema.ts +802 -0
  112. package/convex/searchLogs.d.ts +4 -0
  113. package/convex/searchLogs.d.ts.map +1 -0
  114. package/convex/searchLogs.js +129 -0
  115. package/convex/searchLogs.js.map +1 -0
  116. package/convex/searchLogs.ts +146 -0
  117. package/convex/seedAPILayerAPIs.d.ts +7 -0
  118. package/convex/seedAPILayerAPIs.d.ts.map +1 -0
  119. package/convex/seedAPILayerAPIs.js +177 -0
  120. package/convex/seedAPILayerAPIs.js.map +1 -0
  121. package/convex/seedAPILayerAPIs.ts +191 -0
  122. package/convex/seedDirectCallConfigs.d.ts +2 -0
  123. package/convex/seedDirectCallConfigs.d.ts.map +1 -0
  124. package/convex/seedDirectCallConfigs.js +324 -0
  125. package/convex/seedDirectCallConfigs.js.map +1 -0
  126. package/convex/seedDirectCallConfigs.ts +336 -0
  127. package/convex/seedPratham.d.ts +6 -0
  128. package/convex/seedPratham.d.ts.map +1 -0
  129. package/convex/seedPratham.js +150 -0
  130. package/convex/seedPratham.js.map +1 -0
  131. package/convex/seedPratham.ts +161 -0
  132. package/convex/spendAlerts.d.ts +36 -0
  133. package/convex/spendAlerts.d.ts.map +1 -0
  134. package/convex/spendAlerts.js +380 -0
  135. package/convex/spendAlerts.js.map +1 -0
  136. package/convex/spendAlerts.ts +442 -0
  137. package/convex/stripeActions.d.ts +19 -0
  138. package/convex/stripeActions.d.ts.map +1 -0
  139. package/convex/stripeActions.js +411 -0
  140. package/convex/stripeActions.js.map +1 -0
  141. package/convex/stripeActions.ts +512 -0
  142. package/convex/teams.d.ts +21 -0
  143. package/convex/teams.d.ts.map +1 -0
  144. package/convex/teams.js +215 -0
  145. package/convex/teams.js.map +1 -0
  146. package/convex/teams.ts +243 -0
  147. package/convex/telemetry.d.ts +4 -0
  148. package/convex/telemetry.d.ts.map +1 -0
  149. package/convex/telemetry.js +74 -0
  150. package/convex/telemetry.js.map +1 -0
  151. package/convex/telemetry.ts +81 -0
  152. package/convex/tsconfig.json +25 -0
  153. package/convex/updateAPIStatus.d.ts +6 -0
  154. package/convex/updateAPIStatus.d.ts.map +1 -0
  155. package/convex/updateAPIStatus.js +40 -0
  156. package/convex/updateAPIStatus.js.map +1 -0
  157. package/convex/updateAPIStatus.ts +45 -0
  158. package/convex/usage.d.ts +27 -0
  159. package/convex/usage.d.ts.map +1 -0
  160. package/convex/usage.js +229 -0
  161. package/convex/usage.js.map +1 -0
  162. package/convex/usage.ts +260 -0
  163. package/convex/waitlist.d.ts +4 -0
  164. package/convex/waitlist.d.ts.map +1 -0
  165. package/convex/waitlist.js +49 -0
  166. package/convex/waitlist.js.map +1 -0
  167. package/convex/waitlist.ts +55 -0
  168. package/convex/webhooks.d.ts +12 -0
  169. package/convex/webhooks.d.ts.map +1 -0
  170. package/convex/webhooks.js +410 -0
  171. package/convex/webhooks.js.map +1 -0
  172. package/convex/webhooks.ts +494 -0
  173. package/convex/workspaces.d.ts +31 -0
  174. package/convex/workspaces.d.ts.map +1 -0
  175. package/convex/workspaces.js +975 -0
  176. package/convex/workspaces.js.map +1 -0
  177. package/convex/workspaces.ts +1130 -0
  178. package/dist/bin.js +0 -0
  179. package/dist/capability-router.js +1 -1
  180. package/dist/capability-router.js.map +1 -1
  181. package/dist/execute.d.ts +2 -0
  182. package/dist/execute.d.ts.map +1 -1
  183. package/dist/execute.js +18 -4
  184. package/dist/execute.js.map +1 -1
  185. package/dist/http-api.js +1 -1
  186. package/dist/http-api.js.map +1 -1
  187. package/dist/index.js +1 -1
  188. package/dist/index.js.map +1 -1
  189. package/dist/mcp-analytics.d.ts +32 -0
  190. package/dist/mcp-analytics.d.ts.map +1 -0
  191. package/dist/mcp-analytics.js +130 -0
  192. package/dist/mcp-analytics.js.map +1 -0
  193. package/package.json +1 -1
  194. package/dist/chain-types.d.ts +0 -187
  195. package/dist/chain-types.d.ts.map +0 -1
  196. package/dist/chain-types.js +0 -33
  197. package/dist/chain-types.js.map +0 -1
  198. package/dist/registry/apis.json.bak +0 -248811
  199. package/dist/src/bin.js +0 -17
  200. package/dist/src/capability-router.js +0 -240
  201. package/dist/src/chainExecutor.js +0 -451
  202. package/dist/src/chainResolver.js +0 -518
  203. package/dist/src/cli/commands/doctor.js +0 -324
  204. package/dist/src/cli/commands/mcp-install.js +0 -255
  205. package/dist/src/cli/commands/restore.js +0 -259
  206. package/dist/src/cli/commands/setup.js +0 -205
  207. package/dist/src/cli/commands/uninstall.js +0 -188
  208. package/dist/src/cli/index.js +0 -111
  209. package/dist/src/cli.js +0 -302
  210. package/dist/src/confirmation.js +0 -240
  211. package/dist/src/credentials.js +0 -357
  212. package/dist/src/credits.js +0 -260
  213. package/dist/src/crypto.js +0 -66
  214. package/dist/src/discovery.js +0 -504
  215. package/dist/src/enterprise/env.js +0 -123
  216. package/dist/src/enterprise/script-generator.js +0 -460
  217. package/dist/src/execute-dynamic.js +0 -473
  218. package/dist/src/execute.js +0 -1727
  219. package/dist/src/index.js +0 -2062
  220. package/dist/src/metered.js +0 -80
  221. package/dist/src/open-apis.js +0 -276
  222. package/dist/src/proxy.js +0 -28
  223. package/dist/src/session.js +0 -86
  224. package/dist/src/stripe.js +0 -407
  225. package/dist/src/telemetry.js +0 -49
  226. package/dist/src/types.js +0 -2
  227. package/dist/src/utils/backup.js +0 -181
  228. package/dist/src/utils/config.js +0 -220
  229. package/dist/src/utils/os.js +0 -105
  230. package/dist/src/utils/paths.js +0 -159
@@ -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
- }