@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.
- package/convex/_generated/api.d.ts +115 -0
- package/convex/_generated/api.js +23 -0
- package/convex/_generated/dataModel.d.ts +60 -0
- package/convex/_generated/server.d.ts +143 -0
- package/convex/_generated/server.js +93 -0
- package/convex/adminActivate.d.ts +3 -0
- package/convex/adminActivate.d.ts.map +1 -0
- package/convex/adminActivate.js +47 -0
- package/convex/adminActivate.js.map +1 -0
- package/convex/adminActivate.ts +54 -0
- package/convex/adminStats.d.ts +3 -0
- package/convex/adminStats.d.ts.map +1 -0
- package/convex/adminStats.js +42 -0
- package/convex/adminStats.js.map +1 -0
- package/convex/adminStats.ts +44 -0
- package/convex/agents.d.ts +76 -0
- package/convex/agents.d.ts.map +1 -0
- package/convex/agents.js +699 -0
- package/convex/agents.js.map +1 -0
- package/convex/agents.ts +814 -0
- package/convex/analytics.d.ts +5 -0
- package/convex/analytics.d.ts.map +1 -0
- package/convex/analytics.js +166 -0
- package/convex/analytics.js.map +1 -0
- package/convex/analytics.ts +186 -0
- package/convex/billing.d.ts +88 -0
- package/convex/billing.d.ts.map +1 -0
- package/convex/billing.js +655 -0
- package/convex/billing.js.map +1 -0
- package/convex/billing.ts +791 -0
- package/convex/capabilities.d.ts +9 -0
- package/convex/capabilities.d.ts.map +1 -0
- package/convex/capabilities.js +145 -0
- package/convex/capabilities.js.map +1 -0
- package/convex/capabilities.ts +157 -0
- package/convex/chains.d.ts +68 -0
- package/convex/chains.d.ts.map +1 -0
- package/convex/chains.js +1105 -0
- package/convex/chains.js.map +1 -0
- package/convex/chains.ts +1318 -0
- package/convex/credits.d.ts +25 -0
- package/convex/credits.d.ts.map +1 -0
- package/convex/credits.js +186 -0
- package/convex/credits.js.map +1 -0
- package/convex/credits.ts +211 -0
- package/convex/crons.d.ts +3 -0
- package/convex/crons.d.ts.map +1 -0
- package/convex/crons.js +17 -0
- package/convex/crons.js.map +1 -0
- package/convex/crons.ts +28 -0
- package/convex/directCall.d.ts +72 -0
- package/convex/directCall.d.ts.map +1 -0
- package/convex/directCall.js +627 -0
- package/convex/directCall.js.map +1 -0
- package/convex/directCall.ts +678 -0
- package/convex/earnProgress.d.ts +58 -0
- package/convex/earnProgress.d.ts.map +1 -0
- package/convex/earnProgress.js +649 -0
- package/convex/earnProgress.js.map +1 -0
- package/convex/earnProgress.ts +753 -0
- package/convex/email.d.ts +14 -0
- package/convex/email.d.ts.map +1 -0
- package/convex/email.js +300 -0
- package/convex/email.js.map +1 -0
- package/convex/email.ts +329 -0
- package/convex/feedback.d.ts +7 -0
- package/convex/feedback.d.ts.map +1 -0
- package/convex/feedback.js +227 -0
- package/convex/feedback.js.map +1 -0
- package/convex/feedback.ts +265 -0
- package/convex/http.d.ts +3 -0
- package/convex/http.d.ts.map +1 -0
- package/convex/http.js +1405 -0
- package/convex/http.js.map +1 -0
- package/convex/http.ts +1577 -0
- package/convex/inbound.d.ts +2 -0
- package/convex/inbound.d.ts.map +1 -0
- package/convex/inbound.js +32 -0
- package/convex/inbound.js.map +1 -0
- package/convex/inbound.ts +32 -0
- package/convex/logs.d.ts +38 -0
- package/convex/logs.d.ts.map +1 -0
- package/convex/logs.js +487 -0
- package/convex/logs.js.map +1 -0
- package/convex/logs.ts +550 -0
- package/convex/mou.d.ts +6 -0
- package/convex/mou.d.ts.map +1 -0
- package/convex/mou.js +82 -0
- package/convex/mou.js.map +1 -0
- package/convex/mou.ts +91 -0
- package/convex/providerKeys.d.ts +31 -0
- package/convex/providerKeys.d.ts.map +1 -0
- package/convex/providerKeys.js +257 -0
- package/convex/providerKeys.js.map +1 -0
- package/convex/providerKeys.ts +289 -0
- package/convex/providers.d.ts +32 -0
- package/convex/providers.d.ts.map +1 -0
- package/convex/providers.js +814 -0
- package/convex/providers.js.map +1 -0
- package/convex/providers.ts +909 -0
- package/convex/purchases.d.ts +7 -0
- package/convex/purchases.d.ts.map +1 -0
- package/convex/purchases.js +157 -0
- package/convex/purchases.js.map +1 -0
- package/convex/purchases.ts +183 -0
- package/convex/ratelimit.d.ts +4 -0
- package/convex/ratelimit.d.ts.map +1 -0
- package/convex/ratelimit.js +91 -0
- package/convex/ratelimit.js.map +1 -0
- package/convex/ratelimit.ts +104 -0
- package/convex/schema.ts +802 -0
- package/convex/searchLogs.d.ts +4 -0
- package/convex/searchLogs.d.ts.map +1 -0
- package/convex/searchLogs.js +129 -0
- package/convex/searchLogs.js.map +1 -0
- package/convex/searchLogs.ts +146 -0
- package/convex/seedAPILayerAPIs.d.ts +7 -0
- package/convex/seedAPILayerAPIs.d.ts.map +1 -0
- package/convex/seedAPILayerAPIs.js +177 -0
- package/convex/seedAPILayerAPIs.js.map +1 -0
- package/convex/seedAPILayerAPIs.ts +191 -0
- package/convex/seedDirectCallConfigs.d.ts +2 -0
- package/convex/seedDirectCallConfigs.d.ts.map +1 -0
- package/convex/seedDirectCallConfigs.js +324 -0
- package/convex/seedDirectCallConfigs.js.map +1 -0
- package/convex/seedDirectCallConfigs.ts +336 -0
- package/convex/seedPratham.d.ts +6 -0
- package/convex/seedPratham.d.ts.map +1 -0
- package/convex/seedPratham.js +150 -0
- package/convex/seedPratham.js.map +1 -0
- package/convex/seedPratham.ts +161 -0
- package/convex/spendAlerts.d.ts +36 -0
- package/convex/spendAlerts.d.ts.map +1 -0
- package/convex/spendAlerts.js +380 -0
- package/convex/spendAlerts.js.map +1 -0
- package/convex/spendAlerts.ts +442 -0
- package/convex/stripeActions.d.ts +19 -0
- package/convex/stripeActions.d.ts.map +1 -0
- package/convex/stripeActions.js +411 -0
- package/convex/stripeActions.js.map +1 -0
- package/convex/stripeActions.ts +512 -0
- package/convex/teams.d.ts +21 -0
- package/convex/teams.d.ts.map +1 -0
- package/convex/teams.js +215 -0
- package/convex/teams.js.map +1 -0
- package/convex/teams.ts +243 -0
- package/convex/telemetry.d.ts +4 -0
- package/convex/telemetry.d.ts.map +1 -0
- package/convex/telemetry.js +74 -0
- package/convex/telemetry.js.map +1 -0
- package/convex/telemetry.ts +81 -0
- package/convex/tsconfig.json +25 -0
- package/convex/updateAPIStatus.d.ts +6 -0
- package/convex/updateAPIStatus.d.ts.map +1 -0
- package/convex/updateAPIStatus.js +40 -0
- package/convex/updateAPIStatus.js.map +1 -0
- package/convex/updateAPIStatus.ts +45 -0
- package/convex/usage.d.ts +27 -0
- package/convex/usage.d.ts.map +1 -0
- package/convex/usage.js +229 -0
- package/convex/usage.js.map +1 -0
- package/convex/usage.ts +260 -0
- package/convex/waitlist.d.ts +4 -0
- package/convex/waitlist.d.ts.map +1 -0
- package/convex/waitlist.js +49 -0
- package/convex/waitlist.js.map +1 -0
- package/convex/waitlist.ts +55 -0
- package/convex/webhooks.d.ts +12 -0
- package/convex/webhooks.d.ts.map +1 -0
- package/convex/webhooks.js +410 -0
- package/convex/webhooks.js.map +1 -0
- package/convex/webhooks.ts +494 -0
- package/convex/workspaces.d.ts +31 -0
- package/convex/workspaces.d.ts.map +1 -0
- package/convex/workspaces.js +975 -0
- package/convex/workspaces.js.map +1 -0
- package/convex/workspaces.ts +1130 -0
- package/dist/bin.js +0 -0
- package/dist/capability-router.js +1 -1
- package/dist/capability-router.js.map +1 -1
- package/dist/execute.d.ts +2 -0
- package/dist/execute.d.ts.map +1 -1
- package/dist/execute.js +18 -4
- package/dist/execute.js.map +1 -1
- package/dist/http-api.js +1 -1
- package/dist/http-api.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-analytics.d.ts +32 -0
- package/dist/mcp-analytics.d.ts.map +1 -0
- package/dist/mcp-analytics.js +130 -0
- package/dist/mcp-analytics.js.map +1 -0
- package/package.json +1 -1
- package/dist/chain-types.d.ts +0 -187
- package/dist/chain-types.d.ts.map +0 -1
- package/dist/chain-types.js +0 -33
- package/dist/chain-types.js.map +0 -1
- package/dist/registry/apis.json.bak +0 -248811
- package/dist/src/bin.js +0 -17
- package/dist/src/capability-router.js +0 -240
- package/dist/src/chainExecutor.js +0 -451
- package/dist/src/chainResolver.js +0 -518
- package/dist/src/cli/commands/doctor.js +0 -324
- package/dist/src/cli/commands/mcp-install.js +0 -255
- package/dist/src/cli/commands/restore.js +0 -259
- package/dist/src/cli/commands/setup.js +0 -205
- package/dist/src/cli/commands/uninstall.js +0 -188
- package/dist/src/cli/index.js +0 -111
- package/dist/src/cli.js +0 -302
- package/dist/src/confirmation.js +0 -240
- package/dist/src/credentials.js +0 -357
- package/dist/src/credits.js +0 -260
- package/dist/src/crypto.js +0 -66
- package/dist/src/discovery.js +0 -504
- package/dist/src/enterprise/env.js +0 -123
- package/dist/src/enterprise/script-generator.js +0 -460
- package/dist/src/execute-dynamic.js +0 -473
- package/dist/src/execute.js +0 -1727
- package/dist/src/index.js +0 -2062
- package/dist/src/metered.js +0 -80
- package/dist/src/open-apis.js +0 -276
- package/dist/src/proxy.js +0 -28
- package/dist/src/session.js +0 -86
- package/dist/src/stripe.js +0 -407
- package/dist/src/telemetry.js +0 -49
- package/dist/src/types.js +0 -2
- package/dist/src/utils/backup.js +0 -181
- package/dist/src/utils/config.js +0 -220
- package/dist/src/utils/os.js +0 -105
- package/dist/src/utils/paths.js +0 -159
package/dist/src/execute.js
DELETED
|
@@ -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
|
-
}
|