@almadar/integrations 1.0.0
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/LICENSE +72 -0
- package/README.md +290 -0
- package/dist/factory-rMujCO3M.d.ts +159 -0
- package/dist/index.d.ts +189 -0
- package/dist/index.js +1336 -0
- package/dist/index.js.map +1 -0
- package/dist/mocks/index.d.ts +50 -0
- package/dist/mocks/index.js +342 -0
- package/dist/mocks/index.js.map +1 -0
- package/dist/runtime/index.d.ts +29 -0
- package/dist/runtime/index.js +179 -0
- package/dist/runtime/index.js.map +1 -0
- package/package.json +71 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1336 @@
|
|
|
1
|
+
import integratorsRegistry from '@almadar/patterns/integrators-registry.json';
|
|
2
|
+
import Stripe from 'stripe';
|
|
3
|
+
import { google } from 'googleapis';
|
|
4
|
+
import twilio from 'twilio';
|
|
5
|
+
import sgMail from '@sendgrid/mail';
|
|
6
|
+
import { Resend } from 'resend';
|
|
7
|
+
import { spawn } from 'child_process';
|
|
8
|
+
import { promises } from 'fs';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
|
|
11
|
+
// src/core/logger.ts
|
|
12
|
+
var ConsoleLogger = class {
|
|
13
|
+
constructor(level = "info") {
|
|
14
|
+
this.level = level;
|
|
15
|
+
}
|
|
16
|
+
debug(message, meta) {
|
|
17
|
+
if (this.shouldLog("debug")) {
|
|
18
|
+
console.debug(`[DEBUG] ${message}`, meta || "");
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
info(message, meta) {
|
|
22
|
+
if (this.shouldLog("info")) {
|
|
23
|
+
console.log(`[INFO] ${message}`, meta || "");
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
warn(message, meta) {
|
|
27
|
+
if (this.shouldLog("warn")) {
|
|
28
|
+
console.warn(`[WARN] ${message}`, meta || "");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
error(message, meta) {
|
|
32
|
+
if (this.shouldLog("error")) {
|
|
33
|
+
console.error(`[ERROR] ${message}`, meta || "");
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
shouldLog(level) {
|
|
37
|
+
const levels = ["debug", "info", "warn", "error"];
|
|
38
|
+
return levels.indexOf(level) >= levels.indexOf(this.level);
|
|
39
|
+
}
|
|
40
|
+
};
|
|
41
|
+
function validateParams(integration, action, params) {
|
|
42
|
+
const registry = integratorsRegistry.integrators[integration];
|
|
43
|
+
if (!registry) {
|
|
44
|
+
return {
|
|
45
|
+
valid: false,
|
|
46
|
+
errors: [
|
|
47
|
+
{
|
|
48
|
+
param: "integration",
|
|
49
|
+
message: `Unknown integration: ${integration}`
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
const actionDef = registry.actions.find((a) => a.name === action);
|
|
55
|
+
if (!actionDef) {
|
|
56
|
+
return {
|
|
57
|
+
valid: false,
|
|
58
|
+
errors: [{ param: "action", message: `Unknown action: ${action}` }]
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const errors = [];
|
|
62
|
+
for (const paramDef of actionDef.params) {
|
|
63
|
+
if (paramDef.required && !(paramDef.name in params)) {
|
|
64
|
+
errors.push({
|
|
65
|
+
param: paramDef.name,
|
|
66
|
+
message: `Missing required parameter: ${paramDef.name}`
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
if (paramDef.name in params) {
|
|
70
|
+
const value = params[paramDef.name];
|
|
71
|
+
const expectedType = paramDef.type;
|
|
72
|
+
const actualType = typeof value;
|
|
73
|
+
if (expectedType === "number" && actualType !== "number") {
|
|
74
|
+
errors.push({
|
|
75
|
+
param: paramDef.name,
|
|
76
|
+
message: `Expected ${expectedType}, got ${actualType}`
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
if (expectedType === "string" && actualType !== "string") {
|
|
80
|
+
errors.push({
|
|
81
|
+
param: paramDef.name,
|
|
82
|
+
message: `Expected ${expectedType}, got ${actualType}`
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
if (expectedType === "array" && !Array.isArray(value)) {
|
|
86
|
+
errors.push({
|
|
87
|
+
param: paramDef.name,
|
|
88
|
+
message: `Expected array, got ${actualType}`
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
if (expectedType === "object" && (actualType !== "object" || Array.isArray(value) || value === null)) {
|
|
92
|
+
errors.push({
|
|
93
|
+
param: paramDef.name,
|
|
94
|
+
message: `Expected object, got ${actualType}`
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
return {
|
|
100
|
+
valid: errors.length === 0,
|
|
101
|
+
errors
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// src/core/retry.ts
|
|
106
|
+
async function withRetry(fn, config) {
|
|
107
|
+
const {
|
|
108
|
+
maxAttempts,
|
|
109
|
+
backoffMs,
|
|
110
|
+
maxBackoffMs = 3e4,
|
|
111
|
+
retryableErrors
|
|
112
|
+
} = config;
|
|
113
|
+
let lastError;
|
|
114
|
+
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
115
|
+
try {
|
|
116
|
+
return await fn();
|
|
117
|
+
} catch (error) {
|
|
118
|
+
lastError = error;
|
|
119
|
+
if (error && typeof error === "object" && "code" in error && retryableErrors) {
|
|
120
|
+
const integrationError = error;
|
|
121
|
+
if (!retryableErrors.includes(integrationError.code)) {
|
|
122
|
+
throw error;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
if (attempt === maxAttempts) {
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
128
|
+
const delay = Math.min(
|
|
129
|
+
backoffMs * Math.pow(2, attempt - 1),
|
|
130
|
+
maxBackoffMs
|
|
131
|
+
);
|
|
132
|
+
await new Promise((resolve) => setTimeout(resolve, delay));
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
throw lastError;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
// src/core/BaseIntegration.ts
|
|
139
|
+
var BaseIntegration = class {
|
|
140
|
+
constructor(config) {
|
|
141
|
+
this.config = config;
|
|
142
|
+
this.logger = config.logger || new ConsoleLogger();
|
|
143
|
+
}
|
|
144
|
+
/**
|
|
145
|
+
* Validate action params against registry
|
|
146
|
+
*/
|
|
147
|
+
validateParams(action, params) {
|
|
148
|
+
return validateParams(this.config.name, action, params);
|
|
149
|
+
}
|
|
150
|
+
/**
|
|
151
|
+
* Handle errors uniformly
|
|
152
|
+
*/
|
|
153
|
+
handleError(action, error) {
|
|
154
|
+
this.logger.error(`Integration error in ${this.config.name}.${action}`, {
|
|
155
|
+
error
|
|
156
|
+
});
|
|
157
|
+
const integrationError = error instanceof Error ? error : new Error(String(error));
|
|
158
|
+
return {
|
|
159
|
+
success: false,
|
|
160
|
+
error: integrationError,
|
|
161
|
+
metadata: this.createMetadata(action, 0, 0)
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Create metadata for result
|
|
166
|
+
*/
|
|
167
|
+
createMetadata(action, duration, retries = 0) {
|
|
168
|
+
return {
|
|
169
|
+
integration: this.config.name,
|
|
170
|
+
action,
|
|
171
|
+
duration,
|
|
172
|
+
retries,
|
|
173
|
+
timestamp: Date.now()
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Execute with retry logic
|
|
178
|
+
*/
|
|
179
|
+
async executeWithRetry(fn) {
|
|
180
|
+
if (!this.config.retry) {
|
|
181
|
+
return fn();
|
|
182
|
+
}
|
|
183
|
+
return withRetry(fn, {
|
|
184
|
+
maxAttempts: this.config.retry.maxAttempts,
|
|
185
|
+
backoffMs: this.config.retry.backoffMs,
|
|
186
|
+
maxBackoffMs: this.config.retry.maxBackoffMs,
|
|
187
|
+
retryableErrors: [
|
|
188
|
+
"TIMEOUT_ERROR",
|
|
189
|
+
"NETWORK_ERROR",
|
|
190
|
+
"RATE_LIMIT_ERROR"
|
|
191
|
+
]
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
};
|
|
195
|
+
|
|
196
|
+
// src/registry.ts
|
|
197
|
+
var INTEGRATION_REGISTRY = {};
|
|
198
|
+
function registerIntegration(name, constructor) {
|
|
199
|
+
INTEGRATION_REGISTRY[name] = constructor;
|
|
200
|
+
}
|
|
201
|
+
function getIntegration(name) {
|
|
202
|
+
return INTEGRATION_REGISTRY[name];
|
|
203
|
+
}
|
|
204
|
+
function isKnownIntegration(name) {
|
|
205
|
+
return name in INTEGRATION_REGISTRY;
|
|
206
|
+
}
|
|
207
|
+
function getRegisteredIntegrations() {
|
|
208
|
+
return Object.keys(INTEGRATION_REGISTRY);
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// src/factory.ts
|
|
212
|
+
var IntegrationFactory = class {
|
|
213
|
+
constructor() {
|
|
214
|
+
this.instances = /* @__PURE__ */ new Map();
|
|
215
|
+
this.configs = /* @__PURE__ */ new Map();
|
|
216
|
+
}
|
|
217
|
+
/**
|
|
218
|
+
* Configure an integration (doesn't instantiate yet)
|
|
219
|
+
*/
|
|
220
|
+
configure(name, config) {
|
|
221
|
+
this.configs.set(name, { name, ...config });
|
|
222
|
+
}
|
|
223
|
+
/**
|
|
224
|
+
* Get or create an integration instance
|
|
225
|
+
*/
|
|
226
|
+
get(name) {
|
|
227
|
+
if (this.instances.has(name)) {
|
|
228
|
+
return this.instances.get(name);
|
|
229
|
+
}
|
|
230
|
+
const Constructor = getIntegration(name);
|
|
231
|
+
if (!Constructor) {
|
|
232
|
+
throw new Error(`Unknown integration: ${name}. Make sure it's imported.`);
|
|
233
|
+
}
|
|
234
|
+
const config = this.configs.get(name);
|
|
235
|
+
if (!config) {
|
|
236
|
+
throw new Error(
|
|
237
|
+
`Integration not configured: ${name}. Call configure() first.`
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
const instance = new Constructor(config);
|
|
241
|
+
this.instances.set(name, instance);
|
|
242
|
+
return instance;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* Execute an action on an integration
|
|
246
|
+
*/
|
|
247
|
+
async execute(integration, action, params) {
|
|
248
|
+
const instance = this.get(integration);
|
|
249
|
+
return await instance.execute(action, params);
|
|
250
|
+
}
|
|
251
|
+
/**
|
|
252
|
+
* Check if integration is configured
|
|
253
|
+
*/
|
|
254
|
+
isConfigured(name) {
|
|
255
|
+
return this.configs.has(name);
|
|
256
|
+
}
|
|
257
|
+
/**
|
|
258
|
+
* Clear all instances (useful for testing)
|
|
259
|
+
*/
|
|
260
|
+
clear() {
|
|
261
|
+
this.instances.clear();
|
|
262
|
+
}
|
|
263
|
+
/**
|
|
264
|
+
* Clear all instances and configs
|
|
265
|
+
*/
|
|
266
|
+
reset() {
|
|
267
|
+
this.instances.clear();
|
|
268
|
+
this.configs.clear();
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
var StripeIntegration = class extends BaseIntegration {
|
|
272
|
+
constructor(config) {
|
|
273
|
+
super(config);
|
|
274
|
+
const apiKey = config.env.STRIPE_SECRET_KEY;
|
|
275
|
+
if (!apiKey) {
|
|
276
|
+
throw new Error("STRIPE_SECRET_KEY not configured");
|
|
277
|
+
}
|
|
278
|
+
this.client = new Stripe(apiKey, {
|
|
279
|
+
apiVersion: "2025-02-24.acacia"
|
|
280
|
+
});
|
|
281
|
+
this.logger.info("Stripe integration initialized");
|
|
282
|
+
}
|
|
283
|
+
async execute(action, params) {
|
|
284
|
+
const validation = this.validateParams(action, params);
|
|
285
|
+
if (!validation.valid) {
|
|
286
|
+
return {
|
|
287
|
+
success: false,
|
|
288
|
+
error: {
|
|
289
|
+
name: "IntegrationError",
|
|
290
|
+
message: "Validation failed",
|
|
291
|
+
code: "VALIDATION_ERROR",
|
|
292
|
+
details: validation.errors
|
|
293
|
+
},
|
|
294
|
+
metadata: this.createMetadata(action, 0)
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const startTime = Date.now();
|
|
298
|
+
let retries = 0;
|
|
299
|
+
try {
|
|
300
|
+
let data;
|
|
301
|
+
switch (action) {
|
|
302
|
+
case "createPaymentIntent":
|
|
303
|
+
data = await this.executeWithRetry(
|
|
304
|
+
() => this.createPaymentIntent(params)
|
|
305
|
+
);
|
|
306
|
+
break;
|
|
307
|
+
case "confirmPayment":
|
|
308
|
+
data = await this.executeWithRetry(() => this.confirmPayment(params));
|
|
309
|
+
break;
|
|
310
|
+
case "refund":
|
|
311
|
+
data = await this.executeWithRetry(() => this.refund(params));
|
|
312
|
+
break;
|
|
313
|
+
default:
|
|
314
|
+
throw new Error(`Unknown action: ${action}`);
|
|
315
|
+
}
|
|
316
|
+
return {
|
|
317
|
+
success: true,
|
|
318
|
+
data,
|
|
319
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
320
|
+
};
|
|
321
|
+
} catch (error) {
|
|
322
|
+
return this.handleError(action, error);
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
async createPaymentIntent(params) {
|
|
326
|
+
const { amount, currency, metadata } = params;
|
|
327
|
+
this.logger.debug("Creating payment intent", { amount, currency });
|
|
328
|
+
return await this.client.paymentIntents.create({
|
|
329
|
+
amount,
|
|
330
|
+
currency,
|
|
331
|
+
metadata
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
async confirmPayment(params) {
|
|
335
|
+
const { paymentIntentId } = params;
|
|
336
|
+
this.logger.debug("Confirming payment", { paymentIntentId });
|
|
337
|
+
return await this.client.paymentIntents.confirm(paymentIntentId);
|
|
338
|
+
}
|
|
339
|
+
async refund(params) {
|
|
340
|
+
const { paymentIntentId, amount } = params;
|
|
341
|
+
this.logger.debug("Creating refund", { paymentIntentId, amount });
|
|
342
|
+
return await this.client.refunds.create({
|
|
343
|
+
payment_intent: paymentIntentId,
|
|
344
|
+
amount
|
|
345
|
+
});
|
|
346
|
+
}
|
|
347
|
+
};
|
|
348
|
+
registerIntegration("stripe", StripeIntegration);
|
|
349
|
+
var YouTubeIntegration = class extends BaseIntegration {
|
|
350
|
+
constructor(config) {
|
|
351
|
+
super(config);
|
|
352
|
+
const apiKey = config.env.YOUTUBE_API_KEY;
|
|
353
|
+
if (!apiKey) {
|
|
354
|
+
throw new Error("YOUTUBE_API_KEY not configured");
|
|
355
|
+
}
|
|
356
|
+
this.client = google.youtube({
|
|
357
|
+
version: "v3",
|
|
358
|
+
auth: apiKey
|
|
359
|
+
});
|
|
360
|
+
this.logger.info("YouTube integration initialized");
|
|
361
|
+
}
|
|
362
|
+
async execute(action, params) {
|
|
363
|
+
const validation = this.validateParams(action, params);
|
|
364
|
+
if (!validation.valid) {
|
|
365
|
+
return {
|
|
366
|
+
success: false,
|
|
367
|
+
error: {
|
|
368
|
+
name: "IntegrationError",
|
|
369
|
+
message: "Validation failed",
|
|
370
|
+
code: "VALIDATION_ERROR",
|
|
371
|
+
details: validation.errors
|
|
372
|
+
},
|
|
373
|
+
metadata: this.createMetadata(action, 0)
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
const startTime = Date.now();
|
|
377
|
+
let retries = 0;
|
|
378
|
+
try {
|
|
379
|
+
let data;
|
|
380
|
+
switch (action) {
|
|
381
|
+
case "search":
|
|
382
|
+
data = await this.executeWithRetry(() => this.search(params));
|
|
383
|
+
break;
|
|
384
|
+
case "getVideo":
|
|
385
|
+
data = await this.executeWithRetry(() => this.getVideo(params));
|
|
386
|
+
break;
|
|
387
|
+
case "getChannel":
|
|
388
|
+
data = await this.executeWithRetry(() => this.getChannel(params));
|
|
389
|
+
break;
|
|
390
|
+
default:
|
|
391
|
+
throw new Error(`Unknown action: ${action}`);
|
|
392
|
+
}
|
|
393
|
+
return {
|
|
394
|
+
success: true,
|
|
395
|
+
data,
|
|
396
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
397
|
+
};
|
|
398
|
+
} catch (error) {
|
|
399
|
+
return this.handleError(action, error);
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
async search(params) {
|
|
403
|
+
const { query, maxResults, type } = params;
|
|
404
|
+
this.logger.debug("Searching YouTube", { query, maxResults, type });
|
|
405
|
+
const response = await this.client.search.list({
|
|
406
|
+
part: ["snippet"],
|
|
407
|
+
q: query,
|
|
408
|
+
maxResults: maxResults || 10,
|
|
409
|
+
type: type ? [type] : void 0
|
|
410
|
+
});
|
|
411
|
+
return response.data.items?.map((item) => ({
|
|
412
|
+
videoId: item.id?.videoId,
|
|
413
|
+
title: item.snippet?.title,
|
|
414
|
+
thumbnail: item.snippet?.thumbnails?.default?.url,
|
|
415
|
+
description: item.snippet?.description
|
|
416
|
+
}));
|
|
417
|
+
}
|
|
418
|
+
async getVideo(params) {
|
|
419
|
+
const { videoId } = params;
|
|
420
|
+
this.logger.debug("Getting video details", { videoId });
|
|
421
|
+
const response = await this.client.videos.list({
|
|
422
|
+
part: ["snippet", "statistics"],
|
|
423
|
+
id: [videoId]
|
|
424
|
+
});
|
|
425
|
+
const video = response.data.items?.[0];
|
|
426
|
+
if (!video) {
|
|
427
|
+
throw new Error(`Video not found: ${videoId}`);
|
|
428
|
+
}
|
|
429
|
+
return {
|
|
430
|
+
title: video.snippet?.title,
|
|
431
|
+
description: video.snippet?.description,
|
|
432
|
+
viewCount: video.statistics?.viewCount,
|
|
433
|
+
likeCount: video.statistics?.likeCount
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
async getChannel(params) {
|
|
437
|
+
const { channelId } = params;
|
|
438
|
+
this.logger.debug("Getting channel details", { channelId });
|
|
439
|
+
const response = await this.client.channels.list({
|
|
440
|
+
part: ["snippet", "statistics"],
|
|
441
|
+
id: [channelId]
|
|
442
|
+
});
|
|
443
|
+
const channel = response.data.items?.[0];
|
|
444
|
+
if (!channel) {
|
|
445
|
+
throw new Error(`Channel not found: ${channelId}`);
|
|
446
|
+
}
|
|
447
|
+
return {
|
|
448
|
+
name: channel.snippet?.title,
|
|
449
|
+
description: channel.snippet?.description,
|
|
450
|
+
subscriberCount: channel.statistics?.subscriberCount
|
|
451
|
+
};
|
|
452
|
+
}
|
|
453
|
+
};
|
|
454
|
+
registerIntegration("youtube", YouTubeIntegration);
|
|
455
|
+
var TwilioIntegration = class extends BaseIntegration {
|
|
456
|
+
constructor(config) {
|
|
457
|
+
super(config);
|
|
458
|
+
const accountSid = config.env.TWILIO_ACCOUNT_SID;
|
|
459
|
+
const authToken = config.env.TWILIO_AUTH_TOKEN;
|
|
460
|
+
this.phoneNumber = config.env.TWILIO_PHONE_NUMBER || "";
|
|
461
|
+
if (!accountSid || !authToken) {
|
|
462
|
+
throw new Error("TWILIO_ACCOUNT_SID and TWILIO_AUTH_TOKEN not configured");
|
|
463
|
+
}
|
|
464
|
+
this.client = twilio(accountSid, authToken);
|
|
465
|
+
this.logger.info("Twilio integration initialized");
|
|
466
|
+
}
|
|
467
|
+
async execute(action, params) {
|
|
468
|
+
const validation = this.validateParams(action, params);
|
|
469
|
+
if (!validation.valid) {
|
|
470
|
+
return {
|
|
471
|
+
success: false,
|
|
472
|
+
error: {
|
|
473
|
+
name: "IntegrationError",
|
|
474
|
+
message: "Validation failed",
|
|
475
|
+
code: "VALIDATION_ERROR",
|
|
476
|
+
details: validation.errors
|
|
477
|
+
},
|
|
478
|
+
metadata: this.createMetadata(action, 0)
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
const startTime = Date.now();
|
|
482
|
+
let retries = 0;
|
|
483
|
+
try {
|
|
484
|
+
let data;
|
|
485
|
+
switch (action) {
|
|
486
|
+
case "sendSMS":
|
|
487
|
+
data = await this.executeWithRetry(() => this.sendSMS(params));
|
|
488
|
+
break;
|
|
489
|
+
case "sendWhatsApp":
|
|
490
|
+
data = await this.executeWithRetry(() => this.sendWhatsApp(params));
|
|
491
|
+
break;
|
|
492
|
+
default:
|
|
493
|
+
throw new Error(`Unknown action: ${action}`);
|
|
494
|
+
}
|
|
495
|
+
return {
|
|
496
|
+
success: true,
|
|
497
|
+
data,
|
|
498
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
499
|
+
};
|
|
500
|
+
} catch (error) {
|
|
501
|
+
return this.handleError(action, error);
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
async sendSMS(params) {
|
|
505
|
+
const { to, body } = params;
|
|
506
|
+
this.logger.debug("Sending SMS", { to });
|
|
507
|
+
const message = await this.client.messages.create({
|
|
508
|
+
from: this.phoneNumber,
|
|
509
|
+
to,
|
|
510
|
+
body
|
|
511
|
+
});
|
|
512
|
+
return {
|
|
513
|
+
sid: message.sid,
|
|
514
|
+
status: message.status
|
|
515
|
+
};
|
|
516
|
+
}
|
|
517
|
+
async sendWhatsApp(params) {
|
|
518
|
+
const { to, body } = params;
|
|
519
|
+
this.logger.debug("Sending WhatsApp message", { to });
|
|
520
|
+
const message = await this.client.messages.create({
|
|
521
|
+
from: `whatsapp:${this.phoneNumber}`,
|
|
522
|
+
to: `whatsapp:${to}`,
|
|
523
|
+
body
|
|
524
|
+
});
|
|
525
|
+
return {
|
|
526
|
+
sid: message.sid,
|
|
527
|
+
status: message.status
|
|
528
|
+
};
|
|
529
|
+
}
|
|
530
|
+
};
|
|
531
|
+
registerIntegration("twilio", TwilioIntegration);
|
|
532
|
+
var EmailIntegration = class extends BaseIntegration {
|
|
533
|
+
constructor(config) {
|
|
534
|
+
super(config);
|
|
535
|
+
this.provider = config.env.PROVIDER || "sendgrid";
|
|
536
|
+
this.fromEmail = config.env.FROM_EMAIL || "noreply@example.com";
|
|
537
|
+
if (this.provider === "sendgrid") {
|
|
538
|
+
const apiKey = config.env.SENDGRID_API_KEY;
|
|
539
|
+
if (!apiKey) {
|
|
540
|
+
throw new Error("SENDGRID_API_KEY not configured");
|
|
541
|
+
}
|
|
542
|
+
sgMail.setApiKey(apiKey);
|
|
543
|
+
} else if (this.provider === "resend") {
|
|
544
|
+
const apiKey = config.env.RESEND_API_KEY;
|
|
545
|
+
if (!apiKey) {
|
|
546
|
+
throw new Error("RESEND_API_KEY not configured");
|
|
547
|
+
}
|
|
548
|
+
this.resendClient = new Resend(apiKey);
|
|
549
|
+
}
|
|
550
|
+
this.logger.info(`Email integration initialized (${this.provider})`);
|
|
551
|
+
}
|
|
552
|
+
async execute(action, params) {
|
|
553
|
+
const validation = this.validateParams(action, params);
|
|
554
|
+
if (!validation.valid) {
|
|
555
|
+
return {
|
|
556
|
+
success: false,
|
|
557
|
+
error: {
|
|
558
|
+
name: "IntegrationError",
|
|
559
|
+
message: "Validation failed",
|
|
560
|
+
code: "VALIDATION_ERROR",
|
|
561
|
+
details: validation.errors
|
|
562
|
+
},
|
|
563
|
+
metadata: this.createMetadata(action, 0)
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
const startTime = Date.now();
|
|
567
|
+
let retries = 0;
|
|
568
|
+
try {
|
|
569
|
+
let data;
|
|
570
|
+
switch (action) {
|
|
571
|
+
case "send":
|
|
572
|
+
data = await this.executeWithRetry(() => this.send(params));
|
|
573
|
+
break;
|
|
574
|
+
default:
|
|
575
|
+
throw new Error(`Unknown action: ${action}`);
|
|
576
|
+
}
|
|
577
|
+
return {
|
|
578
|
+
success: true,
|
|
579
|
+
data,
|
|
580
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
581
|
+
};
|
|
582
|
+
} catch (error) {
|
|
583
|
+
return this.handleError(action, error);
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
async send(params) {
|
|
587
|
+
const { to, subject, body, from } = params;
|
|
588
|
+
this.logger.debug("Sending email", { to, subject, provider: this.provider });
|
|
589
|
+
if (this.provider === "sendgrid") {
|
|
590
|
+
return await this.sendViaSendGrid(
|
|
591
|
+
to,
|
|
592
|
+
subject,
|
|
593
|
+
body,
|
|
594
|
+
from || this.fromEmail
|
|
595
|
+
);
|
|
596
|
+
} else if (this.provider === "resend") {
|
|
597
|
+
return await this.sendViaResend(
|
|
598
|
+
to,
|
|
599
|
+
subject,
|
|
600
|
+
body,
|
|
601
|
+
from || this.fromEmail
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
throw new Error(`Unknown email provider: ${this.provider}`);
|
|
605
|
+
}
|
|
606
|
+
async sendViaSendGrid(to, subject, body, from) {
|
|
607
|
+
const msg = {
|
|
608
|
+
to,
|
|
609
|
+
from,
|
|
610
|
+
subject,
|
|
611
|
+
html: body
|
|
612
|
+
};
|
|
613
|
+
const response = await sgMail.send(msg);
|
|
614
|
+
return {
|
|
615
|
+
id: response[0].headers["x-message-id"],
|
|
616
|
+
status: "sent"
|
|
617
|
+
};
|
|
618
|
+
}
|
|
619
|
+
async sendViaResend(to, subject, body, from) {
|
|
620
|
+
if (!this.resendClient) {
|
|
621
|
+
throw new Error("Resend client not initialized");
|
|
622
|
+
}
|
|
623
|
+
const response = await this.resendClient.emails.send({
|
|
624
|
+
from,
|
|
625
|
+
to,
|
|
626
|
+
subject,
|
|
627
|
+
html: body
|
|
628
|
+
});
|
|
629
|
+
return {
|
|
630
|
+
id: response.data?.id,
|
|
631
|
+
status: "sent"
|
|
632
|
+
};
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
registerIntegration("email", EmailIntegration);
|
|
636
|
+
|
|
637
|
+
// src/integrations/llm/index.ts
|
|
638
|
+
var LLMIntegration = class extends BaseIntegration {
|
|
639
|
+
constructor(config) {
|
|
640
|
+
super(config);
|
|
641
|
+
this.provider = config.env.PROVIDER || "anthropic";
|
|
642
|
+
try {
|
|
643
|
+
this.client = this.createLLMClient(config.env);
|
|
644
|
+
this.logger.info(`LLM integration initialized (${this.provider})`);
|
|
645
|
+
} catch (error) {
|
|
646
|
+
throw new Error(`Failed to initialize LLM client: ${error}`);
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
createLLMClient(env) {
|
|
650
|
+
return {
|
|
651
|
+
provider: this.provider,
|
|
652
|
+
apiKey: env.ANTHROPIC_API_KEY || env.OPENAI_API_KEY
|
|
653
|
+
};
|
|
654
|
+
}
|
|
655
|
+
async execute(action, params) {
|
|
656
|
+
const validation = this.validateParams(action, params);
|
|
657
|
+
if (!validation.valid) {
|
|
658
|
+
return {
|
|
659
|
+
success: false,
|
|
660
|
+
error: {
|
|
661
|
+
name: "IntegrationError",
|
|
662
|
+
message: "Validation failed",
|
|
663
|
+
code: "VALIDATION_ERROR",
|
|
664
|
+
details: validation.errors
|
|
665
|
+
},
|
|
666
|
+
metadata: this.createMetadata(action, 0)
|
|
667
|
+
};
|
|
668
|
+
}
|
|
669
|
+
const startTime = Date.now();
|
|
670
|
+
let retries = 0;
|
|
671
|
+
try {
|
|
672
|
+
let data;
|
|
673
|
+
switch (action) {
|
|
674
|
+
case "generate":
|
|
675
|
+
data = await this.executeWithRetry(() => this.generate(params));
|
|
676
|
+
break;
|
|
677
|
+
case "classify":
|
|
678
|
+
data = await this.executeWithRetry(() => this.classify(params));
|
|
679
|
+
break;
|
|
680
|
+
case "extract":
|
|
681
|
+
data = await this.executeWithRetry(() => this.extract(params));
|
|
682
|
+
break;
|
|
683
|
+
case "summarize":
|
|
684
|
+
data = await this.executeWithRetry(() => this.summarize(params));
|
|
685
|
+
break;
|
|
686
|
+
default:
|
|
687
|
+
throw new Error(`Unknown action: ${action}`);
|
|
688
|
+
}
|
|
689
|
+
return {
|
|
690
|
+
success: true,
|
|
691
|
+
data,
|
|
692
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
693
|
+
};
|
|
694
|
+
} catch (error) {
|
|
695
|
+
return this.handleError(action, error);
|
|
696
|
+
}
|
|
697
|
+
}
|
|
698
|
+
async generate(params) {
|
|
699
|
+
const { systemPrompt, userPrompt, model, temperature, maxTokens } = params;
|
|
700
|
+
this.logger.debug("Generating content", { model, temperature });
|
|
701
|
+
return {
|
|
702
|
+
content: "Generated content placeholder",
|
|
703
|
+
usage: { tokens: 0 }
|
|
704
|
+
};
|
|
705
|
+
}
|
|
706
|
+
async classify(params) {
|
|
707
|
+
const { text, categories, model } = params;
|
|
708
|
+
this.logger.debug("Classifying text", { categories });
|
|
709
|
+
return {
|
|
710
|
+
category: categories[0],
|
|
711
|
+
confidence: 0.95,
|
|
712
|
+
reasoning: "Classification reasoning placeholder"
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
async extract(params) {
|
|
716
|
+
const { text, schema, model } = params;
|
|
717
|
+
this.logger.debug("Extracting structured data", { schema });
|
|
718
|
+
return {
|
|
719
|
+
data: {},
|
|
720
|
+
confidence: 0.9
|
|
721
|
+
};
|
|
722
|
+
}
|
|
723
|
+
async summarize(params) {
|
|
724
|
+
const { text, maxLength, style, model } = params;
|
|
725
|
+
this.logger.debug("Summarizing text", { maxLength, style });
|
|
726
|
+
return {
|
|
727
|
+
summary: "Summary placeholder",
|
|
728
|
+
keyPoints: []
|
|
729
|
+
};
|
|
730
|
+
}
|
|
731
|
+
};
|
|
732
|
+
registerIntegration("llm", LLMIntegration);
|
|
733
|
+
|
|
734
|
+
// src/integrations/deepagent/index.ts
|
|
735
|
+
var DeepAgentIntegration = class extends BaseIntegration {
|
|
736
|
+
constructor(config) {
|
|
737
|
+
super(config);
|
|
738
|
+
this.apiUrl = config.env.DEEPAGENT_API_URL || "http://localhost:3000";
|
|
739
|
+
this.apiKey = config.env.DEEPAGENT_API_KEY || "";
|
|
740
|
+
this.logger.info("DeepAgent integration initialized", { apiUrl: this.apiUrl });
|
|
741
|
+
}
|
|
742
|
+
async execute(action, params) {
|
|
743
|
+
const validation = this.validateParams(action, params);
|
|
744
|
+
if (!validation.valid) {
|
|
745
|
+
return {
|
|
746
|
+
success: false,
|
|
747
|
+
error: {
|
|
748
|
+
name: "IntegrationError",
|
|
749
|
+
message: "Validation failed",
|
|
750
|
+
code: "VALIDATION_ERROR",
|
|
751
|
+
details: validation.errors
|
|
752
|
+
},
|
|
753
|
+
metadata: this.createMetadata(action, 0)
|
|
754
|
+
};
|
|
755
|
+
}
|
|
756
|
+
const startTime = Date.now();
|
|
757
|
+
let retries = 0;
|
|
758
|
+
try {
|
|
759
|
+
let data;
|
|
760
|
+
switch (action) {
|
|
761
|
+
case "sendMessage":
|
|
762
|
+
data = await this.executeWithRetry(() => this.sendMessage(params));
|
|
763
|
+
break;
|
|
764
|
+
case "cancelGeneration":
|
|
765
|
+
data = await this.executeWithRetry(
|
|
766
|
+
() => this.cancelGeneration(params)
|
|
767
|
+
);
|
|
768
|
+
break;
|
|
769
|
+
case "validateSchema":
|
|
770
|
+
data = await this.executeWithRetry(() => this.validateSchema(params));
|
|
771
|
+
break;
|
|
772
|
+
case "compileSchema":
|
|
773
|
+
data = await this.executeWithRetry(() => this.compileSchema(params));
|
|
774
|
+
break;
|
|
775
|
+
case "getThreadHistory":
|
|
776
|
+
data = await this.executeWithRetry(
|
|
777
|
+
() => this.getThreadHistory(params)
|
|
778
|
+
);
|
|
779
|
+
break;
|
|
780
|
+
default:
|
|
781
|
+
throw new Error(`Unknown action: ${action}`);
|
|
782
|
+
}
|
|
783
|
+
return {
|
|
784
|
+
success: true,
|
|
785
|
+
data,
|
|
786
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
787
|
+
};
|
|
788
|
+
} catch (error) {
|
|
789
|
+
return this.handleError(action, error);
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
async sendMessage(params) {
|
|
793
|
+
const { message, threadId, skill, context } = params;
|
|
794
|
+
this.logger.debug("Sending message to DeepAgent", { threadId, skill });
|
|
795
|
+
const response = await this.request("/api/agent/message", {
|
|
796
|
+
message,
|
|
797
|
+
threadId,
|
|
798
|
+
skill,
|
|
799
|
+
context
|
|
800
|
+
});
|
|
801
|
+
return response;
|
|
802
|
+
}
|
|
803
|
+
async cancelGeneration(params) {
|
|
804
|
+
const { threadId } = params;
|
|
805
|
+
this.logger.debug("Cancelling generation", { threadId });
|
|
806
|
+
const response = await this.request("/api/agent/cancel", {
|
|
807
|
+
threadId
|
|
808
|
+
});
|
|
809
|
+
return response;
|
|
810
|
+
}
|
|
811
|
+
async validateSchema(params) {
|
|
812
|
+
const { schema } = params;
|
|
813
|
+
this.logger.debug("Validating schema");
|
|
814
|
+
const response = await this.request("/api/schema/validate", {
|
|
815
|
+
schema
|
|
816
|
+
});
|
|
817
|
+
return response;
|
|
818
|
+
}
|
|
819
|
+
async compileSchema(params) {
|
|
820
|
+
const { schema, shell } = params;
|
|
821
|
+
this.logger.debug("Compiling schema", { shell });
|
|
822
|
+
const response = await this.request("/api/schema/compile", {
|
|
823
|
+
schema,
|
|
824
|
+
shell
|
|
825
|
+
});
|
|
826
|
+
return response;
|
|
827
|
+
}
|
|
828
|
+
async getThreadHistory(params) {
|
|
829
|
+
const { threadId } = params;
|
|
830
|
+
this.logger.debug("Getting thread history", { threadId });
|
|
831
|
+
const response = await this.request("/api/agent/history", {
|
|
832
|
+
threadId
|
|
833
|
+
});
|
|
834
|
+
return response;
|
|
835
|
+
}
|
|
836
|
+
async request(endpoint, body) {
|
|
837
|
+
const url = `${this.apiUrl}${endpoint}`;
|
|
838
|
+
const response = await fetch(url, {
|
|
839
|
+
method: "POST",
|
|
840
|
+
headers: {
|
|
841
|
+
"Content-Type": "application/json",
|
|
842
|
+
...this.apiKey ? { Authorization: `Bearer ${this.apiKey}` } : {}
|
|
843
|
+
},
|
|
844
|
+
body: JSON.stringify(body)
|
|
845
|
+
});
|
|
846
|
+
if (!response.ok) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`DeepAgent request failed: ${response.status} ${response.statusText}`
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
return await response.json();
|
|
852
|
+
}
|
|
853
|
+
};
|
|
854
|
+
registerIntegration("deepagent", DeepAgentIntegration);
|
|
855
|
+
|
|
856
|
+
// src/types.ts
|
|
857
|
+
var IntegrationError = class extends Error {
|
|
858
|
+
constructor(message, code = "UNKNOWN_ERROR", details) {
|
|
859
|
+
super(message);
|
|
860
|
+
this.name = "IntegrationError";
|
|
861
|
+
this.code = code;
|
|
862
|
+
this.details = details;
|
|
863
|
+
}
|
|
864
|
+
toJSON() {
|
|
865
|
+
return {
|
|
866
|
+
name: this.name,
|
|
867
|
+
message: this.message,
|
|
868
|
+
code: this.code,
|
|
869
|
+
integration: this.integration,
|
|
870
|
+
action: this.action,
|
|
871
|
+
details: this.details
|
|
872
|
+
};
|
|
873
|
+
}
|
|
874
|
+
};
|
|
875
|
+
|
|
876
|
+
// src/integrations/github/github-git.ts
|
|
877
|
+
async function execGit(args, cwd, env) {
|
|
878
|
+
return new Promise((resolve, reject) => {
|
|
879
|
+
const proc = spawn("git", args, {
|
|
880
|
+
cwd,
|
|
881
|
+
env: { ...process.env, ...env },
|
|
882
|
+
stdio: "pipe"
|
|
883
|
+
});
|
|
884
|
+
let stdout = "";
|
|
885
|
+
let stderr = "";
|
|
886
|
+
proc.stdout?.on("data", (data) => {
|
|
887
|
+
stdout += data.toString();
|
|
888
|
+
});
|
|
889
|
+
proc.stderr?.on("data", (data) => {
|
|
890
|
+
stderr += data.toString();
|
|
891
|
+
});
|
|
892
|
+
proc.on("close", (code) => {
|
|
893
|
+
if (code === 0) {
|
|
894
|
+
resolve({ stdout, stderr });
|
|
895
|
+
} else {
|
|
896
|
+
reject(
|
|
897
|
+
new IntegrationError(
|
|
898
|
+
`Git command failed: ${args.join(" ")}
|
|
899
|
+
${stderr}`,
|
|
900
|
+
"SERVICE_ERROR",
|
|
901
|
+
{ code, stderr }
|
|
902
|
+
)
|
|
903
|
+
);
|
|
904
|
+
}
|
|
905
|
+
});
|
|
906
|
+
proc.on("error", (error) => {
|
|
907
|
+
reject(
|
|
908
|
+
new IntegrationError(
|
|
909
|
+
`Failed to execute git: ${error.message}`,
|
|
910
|
+
"SERVICE_ERROR",
|
|
911
|
+
{ error }
|
|
912
|
+
)
|
|
913
|
+
);
|
|
914
|
+
});
|
|
915
|
+
});
|
|
916
|
+
}
|
|
917
|
+
async function cloneRepo(params, token) {
|
|
918
|
+
const { repoUrl, targetDir, branch, depth = 1 } = params;
|
|
919
|
+
const authUrl = injectTokenIntoUrl(repoUrl, token);
|
|
920
|
+
const args = ["clone"];
|
|
921
|
+
if (depth > 0) {
|
|
922
|
+
args.push("--depth", depth.toString());
|
|
923
|
+
}
|
|
924
|
+
if (branch) {
|
|
925
|
+
args.push("--branch", branch);
|
|
926
|
+
}
|
|
927
|
+
args.push(authUrl, targetDir);
|
|
928
|
+
try {
|
|
929
|
+
await execGit(args, process.cwd());
|
|
930
|
+
await scrubTokenFromRemote(targetDir, repoUrl);
|
|
931
|
+
} catch (error) {
|
|
932
|
+
throw new IntegrationError(
|
|
933
|
+
`Failed to clone repository: ${error instanceof Error ? error.message : String(error)}`,
|
|
934
|
+
"SERVICE_ERROR",
|
|
935
|
+
{ repoUrl, error }
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
}
|
|
939
|
+
async function createBranch(params, workDir) {
|
|
940
|
+
const { branchName, baseBranch } = params;
|
|
941
|
+
const cwd = params.workDir || workDir;
|
|
942
|
+
try {
|
|
943
|
+
if (baseBranch) {
|
|
944
|
+
await execGit(["checkout", baseBranch], cwd);
|
|
945
|
+
}
|
|
946
|
+
await execGit(["checkout", "-b", branchName], cwd);
|
|
947
|
+
} catch (error) {
|
|
948
|
+
throw new IntegrationError(
|
|
949
|
+
`Failed to create branch: ${error instanceof Error ? error.message : String(error)}`,
|
|
950
|
+
"SERVICE_ERROR",
|
|
951
|
+
{ branchName, baseBranch, error }
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
}
|
|
955
|
+
async function commit(params, workDir) {
|
|
956
|
+
const { message, files } = params;
|
|
957
|
+
const cwd = params.workDir || workDir;
|
|
958
|
+
try {
|
|
959
|
+
if (files && files.length > 0) {
|
|
960
|
+
await execGit(["add", ...files], cwd);
|
|
961
|
+
} else {
|
|
962
|
+
await execGit(["add", "."], cwd);
|
|
963
|
+
}
|
|
964
|
+
await execGit(["commit", "-m", message], cwd);
|
|
965
|
+
} catch (error) {
|
|
966
|
+
throw new IntegrationError(
|
|
967
|
+
`Failed to commit: ${error instanceof Error ? error.message : String(error)}`,
|
|
968
|
+
"SERVICE_ERROR",
|
|
969
|
+
{ message, error }
|
|
970
|
+
);
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
async function push(params, workDir, token) {
|
|
974
|
+
const { branchName, force = false } = params;
|
|
975
|
+
const cwd = params.workDir || workDir;
|
|
976
|
+
const protectedBranches = ["main", "master", "production", "prod"];
|
|
977
|
+
if (force && protectedBranches.includes(branchName.toLowerCase())) {
|
|
978
|
+
throw new IntegrationError(
|
|
979
|
+
`Force push to protected branch '${branchName}' is not allowed`,
|
|
980
|
+
"VALIDATION_ERROR"
|
|
981
|
+
);
|
|
982
|
+
}
|
|
983
|
+
try {
|
|
984
|
+
const credHelper = await createTempCredentialHelper(token);
|
|
985
|
+
const args = ["push"];
|
|
986
|
+
if (force) {
|
|
987
|
+
args.push("--force");
|
|
988
|
+
}
|
|
989
|
+
args.push("-u", "origin", branchName);
|
|
990
|
+
await execGit(args, cwd, {
|
|
991
|
+
GIT_ASKPASS: credHelper,
|
|
992
|
+
GIT_TERMINAL_PROMPT: "0"
|
|
993
|
+
});
|
|
994
|
+
await promises.unlink(credHelper);
|
|
995
|
+
} catch (error) {
|
|
996
|
+
throw new IntegrationError(
|
|
997
|
+
`Failed to push: ${error instanceof Error ? error.message : String(error)}`,
|
|
998
|
+
"SERVICE_ERROR",
|
|
999
|
+
{ branchName, error }
|
|
1000
|
+
);
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function injectTokenIntoUrl(repoUrl, token) {
|
|
1004
|
+
const url = new URL(repoUrl);
|
|
1005
|
+
url.username = "x-access-token";
|
|
1006
|
+
url.password = token;
|
|
1007
|
+
return url.toString();
|
|
1008
|
+
}
|
|
1009
|
+
async function scrubTokenFromRemote(workDir, originalUrl) {
|
|
1010
|
+
try {
|
|
1011
|
+
const url = new URL(originalUrl);
|
|
1012
|
+
url.username = "";
|
|
1013
|
+
url.password = "";
|
|
1014
|
+
const cleanUrl = url.toString();
|
|
1015
|
+
await execGit(["remote", "set-url", "origin", cleanUrl], workDir);
|
|
1016
|
+
} catch (error) {
|
|
1017
|
+
console.error("Warning: Failed to scrub token from remote URL:", error);
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
async function createTempCredentialHelper(token) {
|
|
1021
|
+
const tmpDir = process.env.TMPDIR || "/tmp";
|
|
1022
|
+
const helperPath = join(tmpDir, `git-cred-${Date.now()}.sh`);
|
|
1023
|
+
const script = `#!/bin/sh
|
|
1024
|
+
echo "${token}"`;
|
|
1025
|
+
await promises.writeFile(helperPath, script, { mode: 448 });
|
|
1026
|
+
return helperPath;
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// src/integrations/github/github-api.ts
|
|
1030
|
+
async function githubFetch(endpoint, config, options = {}) {
|
|
1031
|
+
const url = `https://api.github.com${endpoint}`;
|
|
1032
|
+
const headers = {
|
|
1033
|
+
"Authorization": `Bearer ${config.token}`,
|
|
1034
|
+
"Accept": "application/vnd.github+json",
|
|
1035
|
+
"X-GitHub-Api-Version": "2022-11-28",
|
|
1036
|
+
...options.headers
|
|
1037
|
+
};
|
|
1038
|
+
try {
|
|
1039
|
+
const response = await fetch(url, {
|
|
1040
|
+
...options,
|
|
1041
|
+
headers
|
|
1042
|
+
});
|
|
1043
|
+
const rateLimit = {
|
|
1044
|
+
remaining: parseInt(response.headers.get("x-ratelimit-remaining") || "0", 10),
|
|
1045
|
+
limit: parseInt(response.headers.get("x-ratelimit-limit") || "5000", 10),
|
|
1046
|
+
reset: parseInt(response.headers.get("x-ratelimit-reset") || "0", 10)
|
|
1047
|
+
};
|
|
1048
|
+
if (!response.ok) {
|
|
1049
|
+
const error = await response.json().catch(() => ({ message: response.statusText }));
|
|
1050
|
+
if (response.status === 429) {
|
|
1051
|
+
throw new IntegrationError(
|
|
1052
|
+
"GitHub API rate limit exceeded",
|
|
1053
|
+
"RATE_LIMIT_ERROR",
|
|
1054
|
+
{ rateLimit, error }
|
|
1055
|
+
);
|
|
1056
|
+
}
|
|
1057
|
+
if (response.status === 401 || response.status === 403) {
|
|
1058
|
+
throw new IntegrationError(
|
|
1059
|
+
`GitHub API authentication failed: ${error.message || response.statusText}`,
|
|
1060
|
+
"AUTH_ERROR",
|
|
1061
|
+
{ status: response.status, error }
|
|
1062
|
+
);
|
|
1063
|
+
}
|
|
1064
|
+
throw new IntegrationError(
|
|
1065
|
+
`GitHub API request failed: ${error.message || response.statusText}`,
|
|
1066
|
+
"SERVICE_ERROR",
|
|
1067
|
+
{ status: response.status, error }
|
|
1068
|
+
);
|
|
1069
|
+
}
|
|
1070
|
+
const data = await response.json();
|
|
1071
|
+
return { data, rateLimit };
|
|
1072
|
+
} catch (error) {
|
|
1073
|
+
if (error instanceof IntegrationError) {
|
|
1074
|
+
throw error;
|
|
1075
|
+
}
|
|
1076
|
+
throw new IntegrationError(
|
|
1077
|
+
`GitHub API request failed: ${error instanceof Error ? error.message : String(error)}`,
|
|
1078
|
+
"NETWORK_ERROR",
|
|
1079
|
+
{ error }
|
|
1080
|
+
);
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
async function createPR(params, config) {
|
|
1084
|
+
const { title, body, baseBranch, headBranch, draft = false } = params;
|
|
1085
|
+
const endpoint = `/repos/${config.owner}/${config.repo}/pulls`;
|
|
1086
|
+
const { data } = await githubFetch(endpoint, config, {
|
|
1087
|
+
method: "POST",
|
|
1088
|
+
body: JSON.stringify({
|
|
1089
|
+
title,
|
|
1090
|
+
body,
|
|
1091
|
+
base: baseBranch,
|
|
1092
|
+
head: headBranch,
|
|
1093
|
+
draft
|
|
1094
|
+
})
|
|
1095
|
+
});
|
|
1096
|
+
return data;
|
|
1097
|
+
}
|
|
1098
|
+
async function getPRComments(params, config) {
|
|
1099
|
+
const { prNumber } = params;
|
|
1100
|
+
const endpoint = `/repos/${config.owner}/${config.repo}/pulls/${prNumber}/comments`;
|
|
1101
|
+
const { data } = await githubFetch(endpoint, config);
|
|
1102
|
+
return data;
|
|
1103
|
+
}
|
|
1104
|
+
async function listIssues(params, config) {
|
|
1105
|
+
const { state = "open", labels = [], limit = 30 } = params;
|
|
1106
|
+
const queryParams = new URLSearchParams({
|
|
1107
|
+
state,
|
|
1108
|
+
per_page: Math.min(limit, 100).toString()
|
|
1109
|
+
});
|
|
1110
|
+
if (labels.length > 0) {
|
|
1111
|
+
queryParams.append("labels", labels.join(","));
|
|
1112
|
+
}
|
|
1113
|
+
const endpoint = `/repos/${config.owner}/${config.repo}/issues?${queryParams}`;
|
|
1114
|
+
const { data } = await githubFetch(endpoint, config);
|
|
1115
|
+
return data;
|
|
1116
|
+
}
|
|
1117
|
+
async function getIssue(params, config) {
|
|
1118
|
+
const { issueNumber } = params;
|
|
1119
|
+
const issueEndpoint = `/repos/${config.owner}/${config.repo}/issues/${issueNumber}`;
|
|
1120
|
+
const { data: issue } = await githubFetch(issueEndpoint, config);
|
|
1121
|
+
const commentsEndpoint = `/repos/${config.owner}/${config.repo}/issues/${issueNumber}/comments`;
|
|
1122
|
+
const { data: comments } = await githubFetch(commentsEndpoint, config);
|
|
1123
|
+
return { issue, comments };
|
|
1124
|
+
}
|
|
1125
|
+
function parseRepoUrl(repoUrl) {
|
|
1126
|
+
try {
|
|
1127
|
+
const url = new URL(repoUrl);
|
|
1128
|
+
const pathParts = url.pathname.split("/").filter(Boolean);
|
|
1129
|
+
if (pathParts.length < 2) {
|
|
1130
|
+
throw new Error("Invalid repository URL format");
|
|
1131
|
+
}
|
|
1132
|
+
const owner = pathParts[0];
|
|
1133
|
+
const repo = pathParts[1].replace(/\.git$/, "");
|
|
1134
|
+
return { owner, repo };
|
|
1135
|
+
} catch (error) {
|
|
1136
|
+
throw new IntegrationError(
|
|
1137
|
+
`Failed to parse repository URL: ${repoUrl}`,
|
|
1138
|
+
"VALIDATION_ERROR",
|
|
1139
|
+
{ error }
|
|
1140
|
+
);
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// src/integrations/github/index.ts
|
|
1145
|
+
var GitHubIntegration = class extends BaseIntegration {
|
|
1146
|
+
constructor(config) {
|
|
1147
|
+
super(config);
|
|
1148
|
+
this.token = config.env.GITHUB_TOKEN;
|
|
1149
|
+
if (!this.token) {
|
|
1150
|
+
throw new Error("GITHUB_TOKEN not configured");
|
|
1151
|
+
}
|
|
1152
|
+
this.owner = config.env.GITHUB_OWNER || "";
|
|
1153
|
+
this.repo = config.env.GITHUB_REPO || "";
|
|
1154
|
+
this.workDir = config.env.GITHUB_WORK_DIR || process.cwd();
|
|
1155
|
+
this.logger.info("GitHub integration initialized", {
|
|
1156
|
+
owner: this.owner,
|
|
1157
|
+
repo: this.repo
|
|
1158
|
+
});
|
|
1159
|
+
}
|
|
1160
|
+
/**
|
|
1161
|
+
* Execute a GitHub action
|
|
1162
|
+
*/
|
|
1163
|
+
async execute(action, params) {
|
|
1164
|
+
const validation = this.validateParams(action, params);
|
|
1165
|
+
if (!validation.valid) {
|
|
1166
|
+
return {
|
|
1167
|
+
success: false,
|
|
1168
|
+
error: {
|
|
1169
|
+
name: "IntegrationError",
|
|
1170
|
+
message: "Validation failed",
|
|
1171
|
+
code: "VALIDATION_ERROR",
|
|
1172
|
+
details: validation.errors
|
|
1173
|
+
},
|
|
1174
|
+
metadata: this.createMetadata(action, 0)
|
|
1175
|
+
};
|
|
1176
|
+
}
|
|
1177
|
+
const startTime = Date.now();
|
|
1178
|
+
let retries = 0;
|
|
1179
|
+
try {
|
|
1180
|
+
let data;
|
|
1181
|
+
switch (action) {
|
|
1182
|
+
case "cloneRepo":
|
|
1183
|
+
data = await this.executeWithRetry(
|
|
1184
|
+
() => this.cloneRepo(params)
|
|
1185
|
+
);
|
|
1186
|
+
break;
|
|
1187
|
+
case "createBranch":
|
|
1188
|
+
data = await this.executeWithRetry(
|
|
1189
|
+
() => this.createBranch(params)
|
|
1190
|
+
);
|
|
1191
|
+
break;
|
|
1192
|
+
case "commit":
|
|
1193
|
+
data = await this.executeWithRetry(
|
|
1194
|
+
() => this.commit(params)
|
|
1195
|
+
);
|
|
1196
|
+
break;
|
|
1197
|
+
case "push":
|
|
1198
|
+
data = await this.executeWithRetry(
|
|
1199
|
+
() => this.push(params)
|
|
1200
|
+
);
|
|
1201
|
+
break;
|
|
1202
|
+
case "createPR":
|
|
1203
|
+
data = await this.executeWithRetry(
|
|
1204
|
+
() => this.createPR(params)
|
|
1205
|
+
);
|
|
1206
|
+
break;
|
|
1207
|
+
case "getPRComments":
|
|
1208
|
+
data = await this.executeWithRetry(
|
|
1209
|
+
() => this.getPRComments(params)
|
|
1210
|
+
);
|
|
1211
|
+
break;
|
|
1212
|
+
case "listIssues":
|
|
1213
|
+
data = await this.executeWithRetry(
|
|
1214
|
+
() => this.listIssues(params)
|
|
1215
|
+
);
|
|
1216
|
+
break;
|
|
1217
|
+
case "getIssue":
|
|
1218
|
+
data = await this.executeWithRetry(
|
|
1219
|
+
() => this.getIssue(params)
|
|
1220
|
+
);
|
|
1221
|
+
break;
|
|
1222
|
+
default:
|
|
1223
|
+
throw new Error(`Unknown GitHub action: ${action}`);
|
|
1224
|
+
}
|
|
1225
|
+
return {
|
|
1226
|
+
success: true,
|
|
1227
|
+
data,
|
|
1228
|
+
metadata: this.createMetadata(action, Date.now() - startTime, retries)
|
|
1229
|
+
};
|
|
1230
|
+
} catch (error) {
|
|
1231
|
+
return this.handleError(action, error);
|
|
1232
|
+
}
|
|
1233
|
+
}
|
|
1234
|
+
/**
|
|
1235
|
+
* Clone a repository
|
|
1236
|
+
*/
|
|
1237
|
+
async cloneRepo(params) {
|
|
1238
|
+
this.logger.debug("Cloning repository", { repoUrl: params.repoUrl });
|
|
1239
|
+
if (!this.owner || !this.repo) {
|
|
1240
|
+
const parsed = parseRepoUrl(params.repoUrl);
|
|
1241
|
+
this.owner = parsed.owner;
|
|
1242
|
+
this.repo = parsed.repo;
|
|
1243
|
+
}
|
|
1244
|
+
await cloneRepo(params, this.token);
|
|
1245
|
+
return {
|
|
1246
|
+
message: `Successfully cloned ${params.repoUrl} to ${params.targetDir}`
|
|
1247
|
+
};
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* Create a branch
|
|
1251
|
+
*/
|
|
1252
|
+
async createBranch(params) {
|
|
1253
|
+
this.logger.debug("Creating branch", { branchName: params.branchName });
|
|
1254
|
+
await createBranch(params, this.workDir);
|
|
1255
|
+
return {
|
|
1256
|
+
message: `Successfully created branch: ${params.branchName}`
|
|
1257
|
+
};
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* Commit changes
|
|
1261
|
+
*/
|
|
1262
|
+
async commit(params) {
|
|
1263
|
+
this.logger.debug("Committing changes", { message: params.message });
|
|
1264
|
+
await commit(params, this.workDir);
|
|
1265
|
+
return {
|
|
1266
|
+
message: `Successfully committed changes: ${params.message}`
|
|
1267
|
+
};
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Push branch
|
|
1271
|
+
*/
|
|
1272
|
+
async push(params) {
|
|
1273
|
+
this.logger.debug("Pushing branch", { branchName: params.branchName });
|
|
1274
|
+
await push(params, this.workDir, this.token);
|
|
1275
|
+
return {
|
|
1276
|
+
message: `Successfully pushed branch: ${params.branchName}`
|
|
1277
|
+
};
|
|
1278
|
+
}
|
|
1279
|
+
/**
|
|
1280
|
+
* Create a pull request
|
|
1281
|
+
*/
|
|
1282
|
+
async createPR(params) {
|
|
1283
|
+
this.logger.debug("Creating pull request", { title: params.title });
|
|
1284
|
+
const apiConfig = this.getAPIConfig();
|
|
1285
|
+
const pr = await createPR(params, apiConfig);
|
|
1286
|
+
this.logger.info("Pull request created", { number: pr.number, url: pr.url });
|
|
1287
|
+
return pr;
|
|
1288
|
+
}
|
|
1289
|
+
/**
|
|
1290
|
+
* Get PR comments
|
|
1291
|
+
*/
|
|
1292
|
+
async getPRComments(params) {
|
|
1293
|
+
this.logger.debug("Getting PR comments", { prNumber: params.prNumber });
|
|
1294
|
+
const apiConfig = this.getAPIConfig();
|
|
1295
|
+
const comments = await getPRComments(params, apiConfig);
|
|
1296
|
+
return { comments };
|
|
1297
|
+
}
|
|
1298
|
+
/**
|
|
1299
|
+
* List issues
|
|
1300
|
+
*/
|
|
1301
|
+
async listIssues(params) {
|
|
1302
|
+
this.logger.debug("Listing issues", params);
|
|
1303
|
+
const apiConfig = this.getAPIConfig();
|
|
1304
|
+
const issues = await listIssues(params, apiConfig);
|
|
1305
|
+
return { issues };
|
|
1306
|
+
}
|
|
1307
|
+
/**
|
|
1308
|
+
* Get issue details
|
|
1309
|
+
*/
|
|
1310
|
+
async getIssue(params) {
|
|
1311
|
+
this.logger.debug("Getting issue", { issueNumber: params.issueNumber });
|
|
1312
|
+
const apiConfig = this.getAPIConfig();
|
|
1313
|
+
const result = await getIssue(params, apiConfig);
|
|
1314
|
+
return result;
|
|
1315
|
+
}
|
|
1316
|
+
/**
|
|
1317
|
+
* Get API config for GitHub API calls
|
|
1318
|
+
*/
|
|
1319
|
+
getAPIConfig() {
|
|
1320
|
+
if (!this.owner || !this.repo) {
|
|
1321
|
+
throw new Error(
|
|
1322
|
+
"GitHub owner and repo must be configured. Either set GITHUB_OWNER/GITHUB_REPO or clone a repository first."
|
|
1323
|
+
);
|
|
1324
|
+
}
|
|
1325
|
+
return {
|
|
1326
|
+
token: this.token,
|
|
1327
|
+
owner: this.owner,
|
|
1328
|
+
repo: this.repo
|
|
1329
|
+
};
|
|
1330
|
+
}
|
|
1331
|
+
};
|
|
1332
|
+
registerIntegration("github", GitHubIntegration);
|
|
1333
|
+
|
|
1334
|
+
export { BaseIntegration, ConsoleLogger, DeepAgentIntegration, EmailIntegration, GitHubIntegration, IntegrationFactory, LLMIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, validateParams, withRetry };
|
|
1335
|
+
//# sourceMappingURL=index.js.map
|
|
1336
|
+
//# sourceMappingURL=index.js.map
|