@almadar/integrations 1.0.14 → 2.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 +21 -72
- package/dist/index.js +1387 -35
- package/dist/index.js.map +1 -1
- package/package.json +11 -5
- package/dist/factory-rMujCO3M.d.ts +0 -159
- package/dist/index.d.ts +0 -189
- package/dist/mocks/index.d.ts +0 -50
- package/dist/runtime/index.d.ts +0 -29
package/dist/index.js
CHANGED
|
@@ -4,9 +4,12 @@ import { google } from 'googleapis';
|
|
|
4
4
|
import twilio from 'twilio';
|
|
5
5
|
import sgMail from '@sendgrid/mail';
|
|
6
6
|
import { Resend } from 'resend';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
7
|
+
import { getAvailableProvider, LLMClient } from '@almadar/llm';
|
|
8
|
+
import { z } from 'zod';
|
|
9
|
+
import { execSync, spawn } from 'child_process';
|
|
10
|
+
import { mkdtempSync, writeFileSync, rmSync, promises } from 'fs';
|
|
9
11
|
import { join } from 'path';
|
|
12
|
+
import { tmpdir } from 'os';
|
|
10
13
|
|
|
11
14
|
// src/core/logger.ts
|
|
12
15
|
var ConsoleLogger = class {
|
|
@@ -633,24 +636,27 @@ var EmailIntegration = class extends BaseIntegration {
|
|
|
633
636
|
}
|
|
634
637
|
};
|
|
635
638
|
registerIntegration("email", EmailIntegration);
|
|
636
|
-
|
|
637
|
-
// src/integrations/llm/index.ts
|
|
638
639
|
var LLMIntegration = class extends BaseIntegration {
|
|
639
640
|
constructor(config) {
|
|
640
641
|
super(config);
|
|
641
|
-
this.
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
} catch (error) {
|
|
646
|
-
throw new Error(`Failed to initialize LLM client: ${error}`);
|
|
647
|
-
}
|
|
642
|
+
this.client = null;
|
|
643
|
+
const configuredProvider = config.env.PROVIDER;
|
|
644
|
+
this.provider = configuredProvider || getAvailableProvider() || "anthropic";
|
|
645
|
+
this.logger.info(`LLM integration initialized (provider: ${this.provider})`);
|
|
648
646
|
}
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
647
|
+
/**
|
|
648
|
+
* Lazily create client — avoids throwing on construction if API key is missing.
|
|
649
|
+
* The client will throw a clear error when actually used without a key.
|
|
650
|
+
*/
|
|
651
|
+
getClient() {
|
|
652
|
+
if (!this.client) {
|
|
653
|
+
this.client = new LLMClient({
|
|
654
|
+
provider: this.provider,
|
|
655
|
+
temperature: 0.7,
|
|
656
|
+
trackTokens: true
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
return this.client;
|
|
654
660
|
}
|
|
655
661
|
async execute(action, params) {
|
|
656
662
|
const validation = this.validateParams(action, params);
|
|
@@ -667,7 +673,6 @@ var LLMIntegration = class extends BaseIntegration {
|
|
|
667
673
|
};
|
|
668
674
|
}
|
|
669
675
|
const startTime = Date.now();
|
|
670
|
-
let retries = 0;
|
|
671
676
|
try {
|
|
672
677
|
let data;
|
|
673
678
|
switch (action) {
|
|
@@ -689,44 +694,91 @@ var LLMIntegration = class extends BaseIntegration {
|
|
|
689
694
|
return {
|
|
690
695
|
success: true,
|
|
691
696
|
data,
|
|
692
|
-
metadata: this.createMetadata(action, Date.now() - startTime
|
|
697
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
693
698
|
};
|
|
694
699
|
} catch (error) {
|
|
695
700
|
return this.handleError(action, error);
|
|
696
701
|
}
|
|
697
702
|
}
|
|
698
703
|
async generate(params) {
|
|
699
|
-
const {
|
|
704
|
+
const {
|
|
705
|
+
systemPrompt,
|
|
706
|
+
userPrompt,
|
|
707
|
+
model,
|
|
708
|
+
temperature,
|
|
709
|
+
maxTokens
|
|
710
|
+
} = params;
|
|
700
711
|
this.logger.debug("Generating content", { model, temperature });
|
|
712
|
+
const client = model ? new LLMClient({ provider: this.provider, model, temperature: temperature ?? 0.7 }) : this.getClient();
|
|
713
|
+
const response = await client.callWithMetadata({
|
|
714
|
+
systemPrompt: systemPrompt || "You are a helpful assistant.",
|
|
715
|
+
userPrompt,
|
|
716
|
+
maxTokens: maxTokens ?? 1024,
|
|
717
|
+
temperature,
|
|
718
|
+
skipSchemaValidation: true
|
|
719
|
+
});
|
|
701
720
|
return {
|
|
702
|
-
content:
|
|
703
|
-
usage:
|
|
721
|
+
content: response.data,
|
|
722
|
+
usage: response.usage ? {
|
|
723
|
+
promptTokens: response.usage.promptTokens,
|
|
724
|
+
completionTokens: response.usage.completionTokens,
|
|
725
|
+
totalTokens: response.usage.totalTokens
|
|
726
|
+
} : { tokens: 0 }
|
|
704
727
|
};
|
|
705
728
|
}
|
|
706
729
|
async classify(params) {
|
|
707
730
|
const { text, categories, model } = params;
|
|
708
731
|
this.logger.debug("Classifying text", { categories });
|
|
709
|
-
|
|
710
|
-
category: categories
|
|
711
|
-
confidence: 0.
|
|
712
|
-
reasoning:
|
|
713
|
-
};
|
|
732
|
+
const ClassificationSchema = z.object({
|
|
733
|
+
category: z.enum(categories),
|
|
734
|
+
confidence: z.number().min(0).max(1),
|
|
735
|
+
reasoning: z.string()
|
|
736
|
+
});
|
|
737
|
+
const client = model ? new LLMClient({ provider: this.provider, model, temperature: 0.1 }) : this.getClient();
|
|
738
|
+
const result = await client.call({
|
|
739
|
+
systemPrompt: `You are a text classifier. Classify the given text into one of these categories: ${categories.join(", ")}. Return JSON with: category, confidence (0-1), and reasoning.`,
|
|
740
|
+
userPrompt: text,
|
|
741
|
+
schema: ClassificationSchema,
|
|
742
|
+
temperature: 0.1
|
|
743
|
+
});
|
|
744
|
+
return result;
|
|
714
745
|
}
|
|
715
746
|
async extract(params) {
|
|
716
747
|
const { text, schema, model } = params;
|
|
717
748
|
this.logger.debug("Extracting structured data", { schema });
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
749
|
+
const ExtractSchema = z.record(z.unknown());
|
|
750
|
+
const client = model ? new LLMClient({ provider: this.provider, model, temperature: 0.2 }) : this.getClient();
|
|
751
|
+
const schemaDescription = JSON.stringify(schema, null, 2);
|
|
752
|
+
const result = await client.call({
|
|
753
|
+
systemPrompt: `You are a structured data extractor. Extract data from the given text according to this schema:
|
|
754
|
+
|
|
755
|
+
${schemaDescription}
|
|
756
|
+
|
|
757
|
+
Return ONLY valid JSON matching the schema.`,
|
|
758
|
+
userPrompt: text,
|
|
759
|
+
schema: ExtractSchema,
|
|
760
|
+
temperature: 0.2,
|
|
761
|
+
skipSchemaValidation: true
|
|
762
|
+
});
|
|
763
|
+
return result;
|
|
722
764
|
}
|
|
723
765
|
async summarize(params) {
|
|
724
766
|
const { text, maxLength, style, model } = params;
|
|
725
767
|
this.logger.debug("Summarizing text", { maxLength, style });
|
|
726
|
-
|
|
727
|
-
summary:
|
|
728
|
-
keyPoints:
|
|
729
|
-
};
|
|
768
|
+
const SummarySchema = z.object({
|
|
769
|
+
summary: z.string(),
|
|
770
|
+
keyPoints: z.array(z.string())
|
|
771
|
+
});
|
|
772
|
+
const styleInstructions = style === "bullet" ? "Use bullet points." : style === "detailed" ? "Be thorough and detailed." : "Be concise.";
|
|
773
|
+
const lengthInstructions = maxLength ? `Keep the summary under ${maxLength} words.` : "";
|
|
774
|
+
const client = model ? new LLMClient({ provider: this.provider, model, temperature: 0.3 }) : this.getClient();
|
|
775
|
+
const result = await client.call({
|
|
776
|
+
systemPrompt: `You are a text summarizer. ${styleInstructions} ${lengthInstructions} Return JSON with: summary (string) and keyPoints (array of strings).`,
|
|
777
|
+
userPrompt: text,
|
|
778
|
+
schema: SummarySchema,
|
|
779
|
+
temperature: 0.3
|
|
780
|
+
});
|
|
781
|
+
return result;
|
|
730
782
|
}
|
|
731
783
|
};
|
|
732
784
|
registerIntegration("llm", LLMIntegration);
|
|
@@ -1330,7 +1382,1307 @@ var GitHubIntegration = class extends BaseIntegration {
|
|
|
1330
1382
|
}
|
|
1331
1383
|
};
|
|
1332
1384
|
registerIntegration("github", GitHubIntegration);
|
|
1385
|
+
var CLIIntegration = class extends BaseIntegration {
|
|
1386
|
+
constructor(config) {
|
|
1387
|
+
super(config);
|
|
1388
|
+
this.logger.info("CLI integration initialized");
|
|
1389
|
+
}
|
|
1390
|
+
async execute(action, params) {
|
|
1391
|
+
const startTime = Date.now();
|
|
1392
|
+
try {
|
|
1393
|
+
let data;
|
|
1394
|
+
switch (action) {
|
|
1395
|
+
case "validate":
|
|
1396
|
+
data = await this.validate(params);
|
|
1397
|
+
break;
|
|
1398
|
+
default:
|
|
1399
|
+
throw new Error(`Unknown CLI action: ${action}`);
|
|
1400
|
+
}
|
|
1401
|
+
return {
|
|
1402
|
+
success: true,
|
|
1403
|
+
data,
|
|
1404
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
1405
|
+
};
|
|
1406
|
+
} catch (error) {
|
|
1407
|
+
return this.handleError(action, error);
|
|
1408
|
+
}
|
|
1409
|
+
}
|
|
1410
|
+
async validate(params) {
|
|
1411
|
+
const { schema } = params;
|
|
1412
|
+
if (!schema || typeof schema !== "string") {
|
|
1413
|
+
throw new Error('validate requires a "schema" parameter (string)');
|
|
1414
|
+
}
|
|
1415
|
+
const tempDir = mkdtempSync(join(tmpdir(), "almadar-validate-"));
|
|
1416
|
+
const schemaPath = join(tempDir, "schema.orb");
|
|
1417
|
+
try {
|
|
1418
|
+
writeFileSync(schemaPath, schema, "utf-8");
|
|
1419
|
+
this.logger.debug("Validating schema", { path: schemaPath });
|
|
1420
|
+
const output = execSync(
|
|
1421
|
+
`npx @almadar/cli validate "${schemaPath}" --format=json`,
|
|
1422
|
+
{
|
|
1423
|
+
encoding: "utf-8",
|
|
1424
|
+
timeout: 3e4,
|
|
1425
|
+
cwd: tempDir,
|
|
1426
|
+
stdio: ["pipe", "pipe", "pipe"]
|
|
1427
|
+
}
|
|
1428
|
+
);
|
|
1429
|
+
const result = JSON.parse(output.trim());
|
|
1430
|
+
return {
|
|
1431
|
+
valid: result.valid ?? true,
|
|
1432
|
+
errors: result.errors || [],
|
|
1433
|
+
warnings: result.warnings || [],
|
|
1434
|
+
summary: result.summary || ""
|
|
1435
|
+
};
|
|
1436
|
+
} catch (error) {
|
|
1437
|
+
const execError = error;
|
|
1438
|
+
if (execError.stdout) {
|
|
1439
|
+
try {
|
|
1440
|
+
const result = JSON.parse(execError.stdout.trim());
|
|
1441
|
+
return {
|
|
1442
|
+
valid: false,
|
|
1443
|
+
errors: result.errors || [],
|
|
1444
|
+
warnings: result.warnings || [],
|
|
1445
|
+
summary: result.summary || ""
|
|
1446
|
+
};
|
|
1447
|
+
} catch {
|
|
1448
|
+
}
|
|
1449
|
+
}
|
|
1450
|
+
throw new Error(
|
|
1451
|
+
`CLI validation failed: ${execError.message || String(error)}`
|
|
1452
|
+
);
|
|
1453
|
+
} finally {
|
|
1454
|
+
try {
|
|
1455
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
1456
|
+
} catch {
|
|
1457
|
+
}
|
|
1458
|
+
}
|
|
1459
|
+
}
|
|
1460
|
+
};
|
|
1461
|
+
registerIntegration("cli", CLIIntegration);
|
|
1462
|
+
|
|
1463
|
+
// src/integrations/redis/index.ts
|
|
1464
|
+
var RedisIntegration = class extends BaseIntegration {
|
|
1465
|
+
constructor(config) {
|
|
1466
|
+
super(config);
|
|
1467
|
+
this.store = /* @__PURE__ */ new Map();
|
|
1468
|
+
this.locks = /* @__PURE__ */ new Map();
|
|
1469
|
+
this.channels = /* @__PURE__ */ new Map();
|
|
1470
|
+
const redisUrl = config.env.REDIS_URL;
|
|
1471
|
+
if (redisUrl) {
|
|
1472
|
+
this.logger.warn(
|
|
1473
|
+
"REDIS_URL is configured but real Redis client is not yet implemented. Falling back to in-memory store.",
|
|
1474
|
+
{ redisUrl }
|
|
1475
|
+
);
|
|
1476
|
+
}
|
|
1477
|
+
this.logger.info("Redis integration initialized (in-memory backend)");
|
|
1478
|
+
}
|
|
1479
|
+
async execute(action, params) {
|
|
1480
|
+
const validation = this.validateParams(action, params);
|
|
1481
|
+
if (!validation.valid) {
|
|
1482
|
+
return {
|
|
1483
|
+
success: false,
|
|
1484
|
+
error: {
|
|
1485
|
+
name: "IntegrationError",
|
|
1486
|
+
message: "Validation failed",
|
|
1487
|
+
code: "VALIDATION_ERROR",
|
|
1488
|
+
details: validation.errors
|
|
1489
|
+
},
|
|
1490
|
+
metadata: this.createMetadata(action, 0)
|
|
1491
|
+
};
|
|
1492
|
+
}
|
|
1493
|
+
const startTime = Date.now();
|
|
1494
|
+
try {
|
|
1495
|
+
let data;
|
|
1496
|
+
switch (action) {
|
|
1497
|
+
case "get":
|
|
1498
|
+
data = await this.executeWithRetry(() => this.getKey(params));
|
|
1499
|
+
break;
|
|
1500
|
+
case "set":
|
|
1501
|
+
data = await this.executeWithRetry(() => this.setKey(params));
|
|
1502
|
+
break;
|
|
1503
|
+
case "delete":
|
|
1504
|
+
data = await this.executeWithRetry(() => this.deleteKey(params));
|
|
1505
|
+
break;
|
|
1506
|
+
case "lock":
|
|
1507
|
+
data = await this.executeWithRetry(() => this.acquireLock(params));
|
|
1508
|
+
break;
|
|
1509
|
+
case "unlock":
|
|
1510
|
+
data = await this.executeWithRetry(() => this.releaseLock(params));
|
|
1511
|
+
break;
|
|
1512
|
+
case "increment":
|
|
1513
|
+
data = await this.executeWithRetry(() => this.incrementKey(params));
|
|
1514
|
+
break;
|
|
1515
|
+
case "expire":
|
|
1516
|
+
data = await this.executeWithRetry(() => this.expireKey(params));
|
|
1517
|
+
break;
|
|
1518
|
+
case "publish":
|
|
1519
|
+
data = await this.executeWithRetry(() => this.publishMessage(params));
|
|
1520
|
+
break;
|
|
1521
|
+
case "subscribe":
|
|
1522
|
+
data = await this.executeWithRetry(
|
|
1523
|
+
() => this.subscribeChannel(params)
|
|
1524
|
+
);
|
|
1525
|
+
break;
|
|
1526
|
+
default:
|
|
1527
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1528
|
+
}
|
|
1529
|
+
return {
|
|
1530
|
+
success: true,
|
|
1531
|
+
data,
|
|
1532
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
1533
|
+
};
|
|
1534
|
+
} catch (error) {
|
|
1535
|
+
return this.handleError(action, error);
|
|
1536
|
+
}
|
|
1537
|
+
}
|
|
1538
|
+
// ---------------------------------------------------------------------------
|
|
1539
|
+
// Helpers
|
|
1540
|
+
// ---------------------------------------------------------------------------
|
|
1541
|
+
/** Remove expired entries on access and return whether a key is alive. */
|
|
1542
|
+
isAlive(key) {
|
|
1543
|
+
const entry = this.store.get(key);
|
|
1544
|
+
if (!entry) return false;
|
|
1545
|
+
if (entry.expiresAt !== void 0 && Date.now() > entry.expiresAt) {
|
|
1546
|
+
this.store.delete(key);
|
|
1547
|
+
return false;
|
|
1548
|
+
}
|
|
1549
|
+
return true;
|
|
1550
|
+
}
|
|
1551
|
+
/** Generate a unique lock identifier. */
|
|
1552
|
+
generateLockId() {
|
|
1553
|
+
return `lock_${Date.now()}_${Math.random().toString(36).slice(2, 10)}`;
|
|
1554
|
+
}
|
|
1555
|
+
// ---------------------------------------------------------------------------
|
|
1556
|
+
// Actions
|
|
1557
|
+
// ---------------------------------------------------------------------------
|
|
1558
|
+
async getKey(params) {
|
|
1559
|
+
const key = params.key;
|
|
1560
|
+
this.logger.debug("Cache GET", { key });
|
|
1561
|
+
if (!this.isAlive(key)) {
|
|
1562
|
+
return { value: null };
|
|
1563
|
+
}
|
|
1564
|
+
const entry = this.store.get(key);
|
|
1565
|
+
return { value: entry ? entry.value : null };
|
|
1566
|
+
}
|
|
1567
|
+
async setKey(params) {
|
|
1568
|
+
const key = params.key;
|
|
1569
|
+
const value = params.value;
|
|
1570
|
+
const ttl = params.ttl;
|
|
1571
|
+
this.logger.debug("Cache SET", { key, ttl });
|
|
1572
|
+
const entry = { value };
|
|
1573
|
+
if (ttl !== void 0 && ttl > 0) {
|
|
1574
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
1575
|
+
}
|
|
1576
|
+
this.store.set(key, entry);
|
|
1577
|
+
return { ok: true };
|
|
1578
|
+
}
|
|
1579
|
+
async deleteKey(params) {
|
|
1580
|
+
const key = params.key;
|
|
1581
|
+
this.logger.debug("Cache DELETE", { key });
|
|
1582
|
+
const existed = this.store.has(key);
|
|
1583
|
+
this.store.delete(key);
|
|
1584
|
+
return { deleted: existed };
|
|
1585
|
+
}
|
|
1586
|
+
async acquireLock(params) {
|
|
1587
|
+
const key = params.key;
|
|
1588
|
+
const ttl = params.ttl ?? 3e4;
|
|
1589
|
+
this.logger.debug("Cache LOCK", { key, ttl });
|
|
1590
|
+
const existingLockId = this.locks.get(key);
|
|
1591
|
+
if (existingLockId) {
|
|
1592
|
+
if (this.isAlive(`__lock:${key}`)) {
|
|
1593
|
+
return { acquired: false, lockId: "" };
|
|
1594
|
+
}
|
|
1595
|
+
this.locks.delete(key);
|
|
1596
|
+
this.store.delete(`__lock:${key}`);
|
|
1597
|
+
}
|
|
1598
|
+
const lockId = this.generateLockId();
|
|
1599
|
+
this.locks.set(key, lockId);
|
|
1600
|
+
this.store.set(`__lock:${key}`, {
|
|
1601
|
+
value: lockId,
|
|
1602
|
+
expiresAt: Date.now() + ttl
|
|
1603
|
+
});
|
|
1604
|
+
return { acquired: true, lockId };
|
|
1605
|
+
}
|
|
1606
|
+
async releaseLock(params) {
|
|
1607
|
+
const key = params.key;
|
|
1608
|
+
const lockId = params.lockId;
|
|
1609
|
+
this.logger.debug("Cache UNLOCK", { key, lockId });
|
|
1610
|
+
const currentLockId = this.locks.get(key);
|
|
1611
|
+
if (currentLockId !== lockId) {
|
|
1612
|
+
return { released: false };
|
|
1613
|
+
}
|
|
1614
|
+
this.locks.delete(key);
|
|
1615
|
+
this.store.delete(`__lock:${key}`);
|
|
1616
|
+
return { released: true };
|
|
1617
|
+
}
|
|
1618
|
+
async incrementKey(params) {
|
|
1619
|
+
const key = params.key;
|
|
1620
|
+
const by = params.by ?? 1;
|
|
1621
|
+
this.logger.debug("Cache INCR", { key, by });
|
|
1622
|
+
let current = 0;
|
|
1623
|
+
if (this.isAlive(key)) {
|
|
1624
|
+
const entry = this.store.get(key);
|
|
1625
|
+
if (entry) {
|
|
1626
|
+
current = typeof entry.value === "number" ? entry.value : 0;
|
|
1627
|
+
}
|
|
1628
|
+
}
|
|
1629
|
+
const newValue = current + by;
|
|
1630
|
+
const existing = this.store.get(key);
|
|
1631
|
+
this.store.set(key, {
|
|
1632
|
+
value: newValue,
|
|
1633
|
+
expiresAt: existing?.expiresAt
|
|
1634
|
+
});
|
|
1635
|
+
return { value: newValue };
|
|
1636
|
+
}
|
|
1637
|
+
async expireKey(params) {
|
|
1638
|
+
const key = params.key;
|
|
1639
|
+
const ttl = params.ttl;
|
|
1640
|
+
this.logger.debug("Cache EXPIRE", { key, ttl });
|
|
1641
|
+
if (!this.isAlive(key)) {
|
|
1642
|
+
return { set: false };
|
|
1643
|
+
}
|
|
1644
|
+
const entry = this.store.get(key);
|
|
1645
|
+
if (!entry) {
|
|
1646
|
+
return { set: false };
|
|
1647
|
+
}
|
|
1648
|
+
entry.expiresAt = Date.now() + ttl * 1e3;
|
|
1649
|
+
this.store.set(key, entry);
|
|
1650
|
+
return { set: true };
|
|
1651
|
+
}
|
|
1652
|
+
async publishMessage(params) {
|
|
1653
|
+
const channel = params.channel;
|
|
1654
|
+
const message = params.message;
|
|
1655
|
+
this.logger.debug("Cache PUBLISH", { channel });
|
|
1656
|
+
const subscribers = this.channels.get(channel);
|
|
1657
|
+
if (!subscribers || subscribers.size === 0) {
|
|
1658
|
+
return { receivers: 0 };
|
|
1659
|
+
}
|
|
1660
|
+
for (const callback of subscribers) {
|
|
1661
|
+
try {
|
|
1662
|
+
callback(message);
|
|
1663
|
+
} catch (err) {
|
|
1664
|
+
this.logger.error("Subscriber callback error", {
|
|
1665
|
+
channel,
|
|
1666
|
+
error: err instanceof Error ? err.message : String(err)
|
|
1667
|
+
});
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
return { receivers: subscribers.size };
|
|
1671
|
+
}
|
|
1672
|
+
async subscribeChannel(params) {
|
|
1673
|
+
const channel = params.channel;
|
|
1674
|
+
this.logger.debug("Cache SUBSCRIBE", { channel });
|
|
1675
|
+
if (!this.channels.has(channel)) {
|
|
1676
|
+
this.channels.set(channel, /* @__PURE__ */ new Set());
|
|
1677
|
+
}
|
|
1678
|
+
return { subscribed: true };
|
|
1679
|
+
}
|
|
1680
|
+
};
|
|
1681
|
+
registerIntegration("redis", RedisIntegration);
|
|
1682
|
+
|
|
1683
|
+
// src/integrations/queue/index.ts
|
|
1684
|
+
var QueueIntegration = class extends BaseIntegration {
|
|
1685
|
+
constructor(config) {
|
|
1686
|
+
super(config);
|
|
1687
|
+
/** Map from queue name to ordered list of job IDs */
|
|
1688
|
+
this.queues = /* @__PURE__ */ new Map();
|
|
1689
|
+
/** Map from job ID to job data */
|
|
1690
|
+
this.jobs = /* @__PURE__ */ new Map();
|
|
1691
|
+
this.logger.info("Queue integration initialized (in-memory backend)");
|
|
1692
|
+
}
|
|
1693
|
+
async execute(action, params) {
|
|
1694
|
+
const validation = this.validateParams(action, params);
|
|
1695
|
+
if (!validation.valid) {
|
|
1696
|
+
return {
|
|
1697
|
+
success: false,
|
|
1698
|
+
error: {
|
|
1699
|
+
name: "IntegrationError",
|
|
1700
|
+
message: "Validation failed",
|
|
1701
|
+
code: "VALIDATION_ERROR",
|
|
1702
|
+
details: validation.errors
|
|
1703
|
+
},
|
|
1704
|
+
metadata: this.createMetadata(action, 0)
|
|
1705
|
+
};
|
|
1706
|
+
}
|
|
1707
|
+
const startTime = Date.now();
|
|
1708
|
+
try {
|
|
1709
|
+
let data;
|
|
1710
|
+
switch (action) {
|
|
1711
|
+
case "enqueue":
|
|
1712
|
+
data = await this.executeWithRetry(() => this.enqueue(params));
|
|
1713
|
+
break;
|
|
1714
|
+
case "dequeue":
|
|
1715
|
+
data = await this.executeWithRetry(() => this.dequeue(params));
|
|
1716
|
+
break;
|
|
1717
|
+
case "status":
|
|
1718
|
+
data = await this.executeWithRetry(() => this.status(params));
|
|
1719
|
+
break;
|
|
1720
|
+
case "complete":
|
|
1721
|
+
data = await this.executeWithRetry(() => this.complete(params));
|
|
1722
|
+
break;
|
|
1723
|
+
case "fail":
|
|
1724
|
+
data = await this.executeWithRetry(() => this.failJob(params));
|
|
1725
|
+
break;
|
|
1726
|
+
case "cancel":
|
|
1727
|
+
data = await this.executeWithRetry(() => this.cancel(params));
|
|
1728
|
+
break;
|
|
1729
|
+
case "size":
|
|
1730
|
+
data = await this.executeWithRetry(() => this.size(params));
|
|
1731
|
+
break;
|
|
1732
|
+
default:
|
|
1733
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1734
|
+
}
|
|
1735
|
+
return {
|
|
1736
|
+
success: true,
|
|
1737
|
+
data,
|
|
1738
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
1739
|
+
};
|
|
1740
|
+
} catch (error) {
|
|
1741
|
+
return this.handleError(action, error);
|
|
1742
|
+
}
|
|
1743
|
+
}
|
|
1744
|
+
// ---------------------------------------------------------------------------
|
|
1745
|
+
// Helpers
|
|
1746
|
+
// ---------------------------------------------------------------------------
|
|
1747
|
+
/** Generate a unique job identifier. */
|
|
1748
|
+
generateJobId() {
|
|
1749
|
+
return `job_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
|
|
1750
|
+
}
|
|
1751
|
+
/** Get or create the queue array for a given queue name. */
|
|
1752
|
+
getQueue(name) {
|
|
1753
|
+
let queue = this.queues.get(name);
|
|
1754
|
+
if (!queue) {
|
|
1755
|
+
queue = [];
|
|
1756
|
+
this.queues.set(name, queue);
|
|
1757
|
+
}
|
|
1758
|
+
return queue;
|
|
1759
|
+
}
|
|
1760
|
+
// ---------------------------------------------------------------------------
|
|
1761
|
+
// Actions
|
|
1762
|
+
// ---------------------------------------------------------------------------
|
|
1763
|
+
async enqueue(params) {
|
|
1764
|
+
const queueName = params.queue;
|
|
1765
|
+
const payload = params.payload;
|
|
1766
|
+
const delay = params.delay ?? 0;
|
|
1767
|
+
const priority = params.priority ?? 0;
|
|
1768
|
+
this.logger.debug("Queue ENQUEUE", { queue: queueName, delay, priority });
|
|
1769
|
+
const jobId = this.generateJobId();
|
|
1770
|
+
const job = {
|
|
1771
|
+
id: jobId,
|
|
1772
|
+
queue: queueName,
|
|
1773
|
+
payload,
|
|
1774
|
+
priority,
|
|
1775
|
+
status: "pending",
|
|
1776
|
+
enqueuedAt: Date.now(),
|
|
1777
|
+
delay
|
|
1778
|
+
};
|
|
1779
|
+
this.jobs.set(jobId, job);
|
|
1780
|
+
const queue = this.getQueue(queueName);
|
|
1781
|
+
let insertIndex = queue.length;
|
|
1782
|
+
for (let i = 0; i < queue.length; i++) {
|
|
1783
|
+
const existingJob = this.jobs.get(queue[i]);
|
|
1784
|
+
if (existingJob && existingJob.priority < priority) {
|
|
1785
|
+
insertIndex = i;
|
|
1786
|
+
break;
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
queue.splice(insertIndex, 0, jobId);
|
|
1790
|
+
return { jobId, position: insertIndex };
|
|
1791
|
+
}
|
|
1792
|
+
async dequeue(params) {
|
|
1793
|
+
const queueName = params.queue;
|
|
1794
|
+
this.logger.debug("Queue DEQUEUE", { queue: queueName });
|
|
1795
|
+
const queue = this.getQueue(queueName);
|
|
1796
|
+
const now = Date.now();
|
|
1797
|
+
for (let i = 0; i < queue.length; i++) {
|
|
1798
|
+
const job = this.jobs.get(queue[i]);
|
|
1799
|
+
if (!job || job.status !== "pending") continue;
|
|
1800
|
+
const readyAt = job.enqueuedAt + job.delay;
|
|
1801
|
+
if (now < readyAt) continue;
|
|
1802
|
+
job.status = "processing";
|
|
1803
|
+
queue.splice(i, 1);
|
|
1804
|
+
return {
|
|
1805
|
+
job: {
|
|
1806
|
+
id: job.id,
|
|
1807
|
+
payload: job.payload,
|
|
1808
|
+
enqueuedAt: job.enqueuedAt,
|
|
1809
|
+
priority: job.priority
|
|
1810
|
+
}
|
|
1811
|
+
};
|
|
1812
|
+
}
|
|
1813
|
+
return { job: null };
|
|
1814
|
+
}
|
|
1815
|
+
async status(params) {
|
|
1816
|
+
const jobId = params.jobId;
|
|
1817
|
+
this.logger.debug("Queue STATUS", { jobId });
|
|
1818
|
+
const job = this.jobs.get(jobId);
|
|
1819
|
+
if (!job) {
|
|
1820
|
+
return { status: "pending", job: null };
|
|
1821
|
+
}
|
|
1822
|
+
return {
|
|
1823
|
+
status: job.status,
|
|
1824
|
+
job: {
|
|
1825
|
+
id: job.id,
|
|
1826
|
+
payload: job.payload,
|
|
1827
|
+
enqueuedAt: job.enqueuedAt,
|
|
1828
|
+
priority: job.priority
|
|
1829
|
+
}
|
|
1830
|
+
};
|
|
1831
|
+
}
|
|
1832
|
+
async complete(params) {
|
|
1833
|
+
const jobId = params.jobId;
|
|
1834
|
+
const result = params.result;
|
|
1835
|
+
this.logger.debug("Queue COMPLETE", { jobId });
|
|
1836
|
+
const job = this.jobs.get(jobId);
|
|
1837
|
+
if (!job || job.status !== "processing") {
|
|
1838
|
+
return { completed: false };
|
|
1839
|
+
}
|
|
1840
|
+
job.status = "completed";
|
|
1841
|
+
job.result = result;
|
|
1842
|
+
return { completed: true };
|
|
1843
|
+
}
|
|
1844
|
+
async failJob(params) {
|
|
1845
|
+
const jobId = params.jobId;
|
|
1846
|
+
const error = params.error;
|
|
1847
|
+
this.logger.debug("Queue FAIL", { jobId });
|
|
1848
|
+
const job = this.jobs.get(jobId);
|
|
1849
|
+
if (!job || job.status !== "processing") {
|
|
1850
|
+
return { failed: false };
|
|
1851
|
+
}
|
|
1852
|
+
job.status = "failed";
|
|
1853
|
+
job.error = error;
|
|
1854
|
+
return { failed: true };
|
|
1855
|
+
}
|
|
1856
|
+
async cancel(params) {
|
|
1857
|
+
const jobId = params.jobId;
|
|
1858
|
+
this.logger.debug("Queue CANCEL", { jobId });
|
|
1859
|
+
const job = this.jobs.get(jobId);
|
|
1860
|
+
if (!job || job.status !== "pending") {
|
|
1861
|
+
return { cancelled: false };
|
|
1862
|
+
}
|
|
1863
|
+
const queue = this.queues.get(job.queue);
|
|
1864
|
+
if (queue) {
|
|
1865
|
+
const idx = queue.indexOf(jobId);
|
|
1866
|
+
if (idx !== -1) {
|
|
1867
|
+
queue.splice(idx, 1);
|
|
1868
|
+
}
|
|
1869
|
+
}
|
|
1870
|
+
this.jobs.delete(jobId);
|
|
1871
|
+
return { cancelled: true };
|
|
1872
|
+
}
|
|
1873
|
+
async size(params) {
|
|
1874
|
+
const queueName = params.queue;
|
|
1875
|
+
this.logger.debug("Queue SIZE", { queue: queueName });
|
|
1876
|
+
let pending = 0;
|
|
1877
|
+
let processing = 0;
|
|
1878
|
+
for (const job of this.jobs.values()) {
|
|
1879
|
+
if (job.queue !== queueName) continue;
|
|
1880
|
+
if (job.status === "pending") pending++;
|
|
1881
|
+
if (job.status === "processing") processing++;
|
|
1882
|
+
}
|
|
1883
|
+
return { size: pending + processing, pending, processing };
|
|
1884
|
+
}
|
|
1885
|
+
};
|
|
1886
|
+
registerIntegration("queue", QueueIntegration);
|
|
1887
|
+
|
|
1888
|
+
// src/integrations/otel/index.ts
|
|
1889
|
+
function randomHexId(bytes) {
|
|
1890
|
+
const arr = [];
|
|
1891
|
+
for (let i = 0; i < bytes; i++) {
|
|
1892
|
+
arr.push(Math.floor(Math.random() * 256).toString(16).padStart(2, "0"));
|
|
1893
|
+
}
|
|
1894
|
+
return arr.join("");
|
|
1895
|
+
}
|
|
1896
|
+
var OtelIntegration = class extends BaseIntegration {
|
|
1897
|
+
constructor(config) {
|
|
1898
|
+
super(config);
|
|
1899
|
+
this.spans = /* @__PURE__ */ new Map();
|
|
1900
|
+
this.metrics = /* @__PURE__ */ new Map();
|
|
1901
|
+
this.logger.info("OTel integration initialized (in-memory backend)");
|
|
1902
|
+
}
|
|
1903
|
+
async execute(action, params) {
|
|
1904
|
+
const validation = this.validateParams(action, params);
|
|
1905
|
+
if (!validation.valid) {
|
|
1906
|
+
return {
|
|
1907
|
+
success: false,
|
|
1908
|
+
error: {
|
|
1909
|
+
name: "IntegrationError",
|
|
1910
|
+
message: "Validation failed",
|
|
1911
|
+
code: "VALIDATION_ERROR",
|
|
1912
|
+
details: validation.errors
|
|
1913
|
+
},
|
|
1914
|
+
metadata: this.createMetadata(action, 0)
|
|
1915
|
+
};
|
|
1916
|
+
}
|
|
1917
|
+
const startTime = Date.now();
|
|
1918
|
+
try {
|
|
1919
|
+
let data;
|
|
1920
|
+
switch (action) {
|
|
1921
|
+
case "startSpan":
|
|
1922
|
+
data = await this.executeWithRetry(() => this.startSpan(params));
|
|
1923
|
+
break;
|
|
1924
|
+
case "endSpan":
|
|
1925
|
+
data = await this.executeWithRetry(() => this.endSpan(params));
|
|
1926
|
+
break;
|
|
1927
|
+
case "addEvent":
|
|
1928
|
+
data = await this.executeWithRetry(() => this.addEvent(params));
|
|
1929
|
+
break;
|
|
1930
|
+
case "recordMetric":
|
|
1931
|
+
data = await this.executeWithRetry(() => this.recordMetric(params));
|
|
1932
|
+
break;
|
|
1933
|
+
case "getSpan":
|
|
1934
|
+
data = await this.executeWithRetry(() => this.getSpan(params));
|
|
1935
|
+
break;
|
|
1936
|
+
case "getMetrics":
|
|
1937
|
+
data = await this.executeWithRetry(() => this.getAllMetrics());
|
|
1938
|
+
break;
|
|
1939
|
+
default:
|
|
1940
|
+
throw new Error(`Unknown action: ${action}`);
|
|
1941
|
+
}
|
|
1942
|
+
return {
|
|
1943
|
+
success: true,
|
|
1944
|
+
data,
|
|
1945
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
1946
|
+
};
|
|
1947
|
+
} catch (error) {
|
|
1948
|
+
return this.handleError(action, error);
|
|
1949
|
+
}
|
|
1950
|
+
}
|
|
1951
|
+
// ---------------------------------------------------------------------------
|
|
1952
|
+
// Actions
|
|
1953
|
+
// ---------------------------------------------------------------------------
|
|
1954
|
+
async startSpan(params) {
|
|
1955
|
+
const name = params.name;
|
|
1956
|
+
const attributes = params.attributes ?? {};
|
|
1957
|
+
const spanId = randomHexId(8);
|
|
1958
|
+
const traceId = params.traceId ?? randomHexId(16);
|
|
1959
|
+
const span = {
|
|
1960
|
+
spanId,
|
|
1961
|
+
traceId,
|
|
1962
|
+
name,
|
|
1963
|
+
startTime: Date.now(),
|
|
1964
|
+
attributes,
|
|
1965
|
+
events: []
|
|
1966
|
+
};
|
|
1967
|
+
this.spans.set(spanId, span);
|
|
1968
|
+
this.logger.debug("Span started", { spanId, traceId, name });
|
|
1969
|
+
return { spanId, traceId };
|
|
1970
|
+
}
|
|
1971
|
+
async endSpan(params) {
|
|
1972
|
+
const spanId = params.spanId;
|
|
1973
|
+
const status = params.status;
|
|
1974
|
+
const span = this.spans.get(spanId);
|
|
1975
|
+
if (!span) {
|
|
1976
|
+
this.logger.warn("endSpan called for unknown span", { spanId });
|
|
1977
|
+
return { ended: false };
|
|
1978
|
+
}
|
|
1979
|
+
span.endTime = Date.now();
|
|
1980
|
+
if (status) {
|
|
1981
|
+
span.status = status;
|
|
1982
|
+
}
|
|
1983
|
+
this.logger.debug("Span ended", { spanId, status, duration: span.endTime - span.startTime });
|
|
1984
|
+
return { ended: true };
|
|
1985
|
+
}
|
|
1986
|
+
async addEvent(params) {
|
|
1987
|
+
const spanId = params.spanId;
|
|
1988
|
+
const name = params.name;
|
|
1989
|
+
const attributes = params.attributes;
|
|
1990
|
+
const span = this.spans.get(spanId);
|
|
1991
|
+
if (!span) {
|
|
1992
|
+
this.logger.warn("addEvent called for unknown span", { spanId });
|
|
1993
|
+
return { added: false };
|
|
1994
|
+
}
|
|
1995
|
+
span.events.push({
|
|
1996
|
+
name,
|
|
1997
|
+
timestamp: Date.now(),
|
|
1998
|
+
attributes
|
|
1999
|
+
});
|
|
2000
|
+
this.logger.debug("Event added to span", { spanId, eventName: name });
|
|
2001
|
+
return { added: true };
|
|
2002
|
+
}
|
|
2003
|
+
async recordMetric(params) {
|
|
2004
|
+
const name = params.name;
|
|
2005
|
+
const value = params.value;
|
|
2006
|
+
const type = params.type ?? "counter";
|
|
2007
|
+
const labels = params.labels ?? {};
|
|
2008
|
+
const existing = this.metrics.get(name);
|
|
2009
|
+
if (existing) {
|
|
2010
|
+
switch (type) {
|
|
2011
|
+
case "counter":
|
|
2012
|
+
existing.value += value;
|
|
2013
|
+
break;
|
|
2014
|
+
case "gauge":
|
|
2015
|
+
existing.value = value;
|
|
2016
|
+
break;
|
|
2017
|
+
case "histogram":
|
|
2018
|
+
existing.values.push(value);
|
|
2019
|
+
break;
|
|
2020
|
+
}
|
|
2021
|
+
existing.labels = labels;
|
|
2022
|
+
} else {
|
|
2023
|
+
this.metrics.set(name, {
|
|
2024
|
+
name,
|
|
2025
|
+
type,
|
|
2026
|
+
value: type === "histogram" ? 0 : value,
|
|
2027
|
+
values: type === "histogram" ? [value] : [],
|
|
2028
|
+
labels
|
|
2029
|
+
});
|
|
2030
|
+
}
|
|
2031
|
+
this.logger.debug("Metric recorded", { name, value, type });
|
|
2032
|
+
return { recorded: true };
|
|
2033
|
+
}
|
|
2034
|
+
async getSpan(params) {
|
|
2035
|
+
const spanId = params.spanId;
|
|
2036
|
+
const span = this.spans.get(spanId);
|
|
2037
|
+
return span ?? null;
|
|
2038
|
+
}
|
|
2039
|
+
async getAllMetrics() {
|
|
2040
|
+
const result = {};
|
|
2041
|
+
for (const [key, metric] of this.metrics) {
|
|
2042
|
+
result[key] = metric;
|
|
2043
|
+
}
|
|
2044
|
+
return result;
|
|
2045
|
+
}
|
|
2046
|
+
};
|
|
2047
|
+
registerIntegration("otel", OtelIntegration);
|
|
2048
|
+
|
|
2049
|
+
// src/integrations/oauth/index.ts
|
|
2050
|
+
var PROVIDER_AUTH_URLS = {
|
|
2051
|
+
google: "https://accounts.google.com/o/oauth2/v2/auth",
|
|
2052
|
+
github: "https://github.com/login/oauth/authorize",
|
|
2053
|
+
auth0: "https://auth.example.com/authorize"
|
|
2054
|
+
};
|
|
2055
|
+
var OAuthIntegration = class extends BaseIntegration {
|
|
2056
|
+
constructor(config) {
|
|
2057
|
+
super(config);
|
|
2058
|
+
/** Maps state token -> provider for pending authorization flows */
|
|
2059
|
+
this.states = /* @__PURE__ */ new Map();
|
|
2060
|
+
/** Maps access token -> token set */
|
|
2061
|
+
this.tokens = /* @__PURE__ */ new Map();
|
|
2062
|
+
/** Maps refresh token -> access token for refresh lookups */
|
|
2063
|
+
this.refreshIndex = /* @__PURE__ */ new Map();
|
|
2064
|
+
/** Maps access token -> mock user session */
|
|
2065
|
+
this.sessions = /* @__PURE__ */ new Map();
|
|
2066
|
+
this.logger.info("OAuth integration initialized (mock backend)");
|
|
2067
|
+
}
|
|
2068
|
+
async execute(action, params) {
|
|
2069
|
+
const validation = this.validateParams(action, params);
|
|
2070
|
+
if (!validation.valid) {
|
|
2071
|
+
return {
|
|
2072
|
+
success: false,
|
|
2073
|
+
error: {
|
|
2074
|
+
name: "IntegrationError",
|
|
2075
|
+
message: "Validation failed",
|
|
2076
|
+
code: "VALIDATION_ERROR",
|
|
2077
|
+
details: validation.errors
|
|
2078
|
+
},
|
|
2079
|
+
metadata: this.createMetadata(action, 0)
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
const startTime = Date.now();
|
|
2083
|
+
try {
|
|
2084
|
+
let data;
|
|
2085
|
+
switch (action) {
|
|
2086
|
+
case "authorize":
|
|
2087
|
+
data = await this.executeWithRetry(() => this.authorize(params));
|
|
2088
|
+
break;
|
|
2089
|
+
case "token":
|
|
2090
|
+
data = await this.executeWithRetry(() => this.token(params));
|
|
2091
|
+
break;
|
|
2092
|
+
case "refresh":
|
|
2093
|
+
data = await this.executeWithRetry(() => this.refresh(params));
|
|
2094
|
+
break;
|
|
2095
|
+
case "revoke":
|
|
2096
|
+
data = await this.executeWithRetry(() => this.revoke(params));
|
|
2097
|
+
break;
|
|
2098
|
+
case "userinfo":
|
|
2099
|
+
data = await this.executeWithRetry(() => this.userinfo(params));
|
|
2100
|
+
break;
|
|
2101
|
+
default:
|
|
2102
|
+
throw new Error(`Unknown action: ${action}`);
|
|
2103
|
+
}
|
|
2104
|
+
return {
|
|
2105
|
+
success: true,
|
|
2106
|
+
data,
|
|
2107
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
2108
|
+
};
|
|
2109
|
+
} catch (error) {
|
|
2110
|
+
return this.handleError(action, error);
|
|
2111
|
+
}
|
|
2112
|
+
}
|
|
2113
|
+
// ---------------------------------------------------------------------------
|
|
2114
|
+
// Helpers
|
|
2115
|
+
// ---------------------------------------------------------------------------
|
|
2116
|
+
/** Generate a random hex token of the given byte length. */
|
|
2117
|
+
generateToken(bytes = 32) {
|
|
2118
|
+
const chars = "abcdef0123456789";
|
|
2119
|
+
let result = "";
|
|
2120
|
+
for (let i = 0; i < bytes * 2; i++) {
|
|
2121
|
+
result += chars[Math.floor(Math.random() * chars.length)];
|
|
2122
|
+
}
|
|
2123
|
+
return result;
|
|
2124
|
+
}
|
|
2125
|
+
/** Generate a mock user profile from a provider and access token. */
|
|
2126
|
+
generateMockUser(provider, sub) {
|
|
2127
|
+
return {
|
|
2128
|
+
sub,
|
|
2129
|
+
email: `user-${sub.slice(0, 8)}@${provider}.example.com`,
|
|
2130
|
+
name: `Mock User (${provider})`,
|
|
2131
|
+
picture: `https://${provider}.example.com/avatar/${sub.slice(0, 8)}.png`
|
|
2132
|
+
};
|
|
2133
|
+
}
|
|
2134
|
+
// ---------------------------------------------------------------------------
|
|
2135
|
+
// Actions
|
|
2136
|
+
// ---------------------------------------------------------------------------
|
|
2137
|
+
async authorize(params) {
|
|
2138
|
+
const provider = params.provider;
|
|
2139
|
+
const scopes = params.scopes;
|
|
2140
|
+
const redirectUri = params.redirectUri;
|
|
2141
|
+
this.logger.debug("OAuth AUTHORIZE", { provider, scopes, redirectUri });
|
|
2142
|
+
const state = this.generateToken(16);
|
|
2143
|
+
this.states.set(state, provider);
|
|
2144
|
+
const baseUrl = PROVIDER_AUTH_URLS[provider];
|
|
2145
|
+
const queryParams = new URLSearchParams({
|
|
2146
|
+
response_type: "code",
|
|
2147
|
+
scope: scopes.join(" "),
|
|
2148
|
+
redirect_uri: redirectUri,
|
|
2149
|
+
state,
|
|
2150
|
+
client_id: this.config.env.CLIENT_ID ?? "mock-client-id"
|
|
2151
|
+
});
|
|
2152
|
+
const authUrl = `${baseUrl}?${queryParams.toString()}`;
|
|
2153
|
+
return { authUrl, state };
|
|
2154
|
+
}
|
|
2155
|
+
async token(params) {
|
|
2156
|
+
const code = params.code;
|
|
2157
|
+
const state = params.state;
|
|
2158
|
+
this.logger.debug("OAuth TOKEN", { code, state });
|
|
2159
|
+
const provider = this.states.get(state);
|
|
2160
|
+
if (!provider) {
|
|
2161
|
+
throw new Error(
|
|
2162
|
+
`Invalid or expired state token: ${state}`
|
|
2163
|
+
);
|
|
2164
|
+
}
|
|
2165
|
+
this.states.delete(state);
|
|
2166
|
+
const accessToken = this.generateToken(32);
|
|
2167
|
+
const refreshToken = this.generateToken(32);
|
|
2168
|
+
const expiresIn = 3600;
|
|
2169
|
+
const tokenSet = {
|
|
2170
|
+
accessToken,
|
|
2171
|
+
refreshToken,
|
|
2172
|
+
expiresAt: Date.now() + expiresIn * 1e3
|
|
2173
|
+
};
|
|
2174
|
+
this.tokens.set(accessToken, tokenSet);
|
|
2175
|
+
this.refreshIndex.set(refreshToken, accessToken);
|
|
2176
|
+
const sub = this.generateToken(8);
|
|
2177
|
+
this.sessions.set(accessToken, this.generateMockUser(provider, sub));
|
|
2178
|
+
return { accessToken, refreshToken, expiresIn, tokenType: "bearer" };
|
|
2179
|
+
}
|
|
2180
|
+
async refresh(params) {
|
|
2181
|
+
const refreshToken = params.refreshToken;
|
|
2182
|
+
this.logger.debug("OAuth REFRESH", { refreshToken: refreshToken.slice(0, 8) + "..." });
|
|
2183
|
+
const oldAccessToken = this.refreshIndex.get(refreshToken);
|
|
2184
|
+
if (!oldAccessToken) {
|
|
2185
|
+
throw new Error("Invalid refresh token");
|
|
2186
|
+
}
|
|
2187
|
+
const userInfo = this.sessions.get(oldAccessToken);
|
|
2188
|
+
this.tokens.delete(oldAccessToken);
|
|
2189
|
+
this.sessions.delete(oldAccessToken);
|
|
2190
|
+
const newAccessToken = this.generateToken(32);
|
|
2191
|
+
const expiresIn = 3600;
|
|
2192
|
+
const tokenSet = {
|
|
2193
|
+
accessToken: newAccessToken,
|
|
2194
|
+
refreshToken,
|
|
2195
|
+
expiresAt: Date.now() + expiresIn * 1e3
|
|
2196
|
+
};
|
|
2197
|
+
this.tokens.set(newAccessToken, tokenSet);
|
|
2198
|
+
this.refreshIndex.set(refreshToken, newAccessToken);
|
|
2199
|
+
if (userInfo) {
|
|
2200
|
+
this.sessions.set(newAccessToken, userInfo);
|
|
2201
|
+
}
|
|
2202
|
+
return { accessToken: newAccessToken, expiresIn };
|
|
2203
|
+
}
|
|
2204
|
+
async revoke(params) {
|
|
2205
|
+
const token = params.token;
|
|
2206
|
+
this.logger.debug("OAuth REVOKE", { token: token.slice(0, 8) + "..." });
|
|
2207
|
+
const tokenSet = this.tokens.get(token);
|
|
2208
|
+
if (tokenSet) {
|
|
2209
|
+
this.refreshIndex.delete(tokenSet.refreshToken);
|
|
2210
|
+
this.tokens.delete(token);
|
|
2211
|
+
this.sessions.delete(token);
|
|
2212
|
+
return { revoked: true };
|
|
2213
|
+
}
|
|
2214
|
+
const accessToken = this.refreshIndex.get(token);
|
|
2215
|
+
if (accessToken) {
|
|
2216
|
+
this.tokens.delete(accessToken);
|
|
2217
|
+
this.sessions.delete(accessToken);
|
|
2218
|
+
this.refreshIndex.delete(token);
|
|
2219
|
+
return { revoked: true };
|
|
2220
|
+
}
|
|
2221
|
+
return { revoked: false };
|
|
2222
|
+
}
|
|
2223
|
+
async userinfo(params) {
|
|
2224
|
+
const accessToken = params.accessToken;
|
|
2225
|
+
this.logger.debug("OAuth USERINFO", { accessToken: accessToken.slice(0, 8) + "..." });
|
|
2226
|
+
const tokenSet = this.tokens.get(accessToken);
|
|
2227
|
+
if (!tokenSet) {
|
|
2228
|
+
throw new Error("Invalid access token");
|
|
2229
|
+
}
|
|
2230
|
+
if (Date.now() > tokenSet.expiresAt) {
|
|
2231
|
+
this.tokens.delete(accessToken);
|
|
2232
|
+
this.sessions.delete(accessToken);
|
|
2233
|
+
throw new Error("Access token expired");
|
|
2234
|
+
}
|
|
2235
|
+
const userInfo = this.sessions.get(accessToken);
|
|
2236
|
+
if (!userInfo) {
|
|
2237
|
+
throw new Error("No session found for access token");
|
|
2238
|
+
}
|
|
2239
|
+
return userInfo;
|
|
2240
|
+
}
|
|
2241
|
+
};
|
|
2242
|
+
registerIntegration("oauth", OAuthIntegration);
|
|
2243
|
+
|
|
2244
|
+
// src/integrations/storage/index.ts
|
|
2245
|
+
var StorageIntegration = class extends BaseIntegration {
|
|
2246
|
+
constructor(config) {
|
|
2247
|
+
super(config);
|
|
2248
|
+
this.objects = /* @__PURE__ */ new Map();
|
|
2249
|
+
const storageUrl = config.env.STORAGE_URL;
|
|
2250
|
+
if (storageUrl) {
|
|
2251
|
+
this.logger.warn(
|
|
2252
|
+
"STORAGE_URL is configured but real storage client is not yet implemented. Falling back to in-memory store.",
|
|
2253
|
+
{ storageUrl }
|
|
2254
|
+
);
|
|
2255
|
+
}
|
|
2256
|
+
this.logger.info("Storage integration initialized (in-memory backend)");
|
|
2257
|
+
}
|
|
2258
|
+
async execute(action, params) {
|
|
2259
|
+
const validation = this.validateParams(action, params);
|
|
2260
|
+
if (!validation.valid) {
|
|
2261
|
+
return {
|
|
2262
|
+
success: false,
|
|
2263
|
+
error: {
|
|
2264
|
+
name: "IntegrationError",
|
|
2265
|
+
message: "Validation failed",
|
|
2266
|
+
code: "VALIDATION_ERROR",
|
|
2267
|
+
details: validation.errors
|
|
2268
|
+
},
|
|
2269
|
+
metadata: this.createMetadata(action, 0)
|
|
2270
|
+
};
|
|
2271
|
+
}
|
|
2272
|
+
const startTime = Date.now();
|
|
2273
|
+
try {
|
|
2274
|
+
let data;
|
|
2275
|
+
switch (action) {
|
|
2276
|
+
case "upload":
|
|
2277
|
+
data = await this.executeWithRetry(() => this.upload(params));
|
|
2278
|
+
break;
|
|
2279
|
+
case "download":
|
|
2280
|
+
data = await this.executeWithRetry(() => this.download(params));
|
|
2281
|
+
break;
|
|
2282
|
+
case "list":
|
|
2283
|
+
data = await this.executeWithRetry(() => this.list(params));
|
|
2284
|
+
break;
|
|
2285
|
+
case "delete":
|
|
2286
|
+
data = await this.executeWithRetry(() => this.deleteObject(params));
|
|
2287
|
+
break;
|
|
2288
|
+
case "getSignedUrl":
|
|
2289
|
+
data = await this.executeWithRetry(() => this.getSignedUrl(params));
|
|
2290
|
+
break;
|
|
2291
|
+
default:
|
|
2292
|
+
throw new Error(`Unknown action: ${action}`);
|
|
2293
|
+
}
|
|
2294
|
+
return {
|
|
2295
|
+
success: true,
|
|
2296
|
+
data,
|
|
2297
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
2298
|
+
};
|
|
2299
|
+
} catch (error) {
|
|
2300
|
+
return this.handleError(action, error);
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
2303
|
+
// ---------------------------------------------------------------------------
|
|
2304
|
+
// Helpers
|
|
2305
|
+
// ---------------------------------------------------------------------------
|
|
2306
|
+
/** Build a composite key from bucket and object key. */
|
|
2307
|
+
compositeKey(bucket, key) {
|
|
2308
|
+
return `${bucket}/${key}`;
|
|
2309
|
+
}
|
|
2310
|
+
/** Generate a deterministic etag from content. */
|
|
2311
|
+
generateEtag(content) {
|
|
2312
|
+
const raw = typeof content === "string" ? content : JSON.stringify(content);
|
|
2313
|
+
let hash = 0;
|
|
2314
|
+
for (let i = 0; i < raw.length; i++) {
|
|
2315
|
+
const ch = raw.charCodeAt(i);
|
|
2316
|
+
hash = (hash << 5) - hash + ch | 0;
|
|
2317
|
+
}
|
|
2318
|
+
return `"${Math.abs(hash).toString(16).padStart(8, "0")}"`;
|
|
2319
|
+
}
|
|
2320
|
+
/** Compute the byte size of content. */
|
|
2321
|
+
computeSize(content) {
|
|
2322
|
+
if (typeof content === "string") {
|
|
2323
|
+
return new TextEncoder().encode(content).byteLength;
|
|
2324
|
+
}
|
|
2325
|
+
return JSON.stringify(content).length;
|
|
2326
|
+
}
|
|
2327
|
+
// ---------------------------------------------------------------------------
|
|
2328
|
+
// Actions
|
|
2329
|
+
// ---------------------------------------------------------------------------
|
|
2330
|
+
async upload(params) {
|
|
2331
|
+
const bucket = params.bucket;
|
|
2332
|
+
const key = params.key;
|
|
2333
|
+
const content = params.content;
|
|
2334
|
+
const contentType = params.contentType ?? "application/octet-stream";
|
|
2335
|
+
const metadata = params.metadata ?? {};
|
|
2336
|
+
this.logger.debug("Storage UPLOAD", { bucket, key, contentType });
|
|
2337
|
+
const size = this.computeSize(content);
|
|
2338
|
+
const etag = this.generateEtag(content);
|
|
2339
|
+
const obj = {
|
|
2340
|
+
content,
|
|
2341
|
+
contentType,
|
|
2342
|
+
size,
|
|
2343
|
+
metadata,
|
|
2344
|
+
lastModified: Date.now(),
|
|
2345
|
+
etag
|
|
2346
|
+
};
|
|
2347
|
+
this.objects.set(this.compositeKey(bucket, key), obj);
|
|
2348
|
+
return { key, bucket, size, etag };
|
|
2349
|
+
}
|
|
2350
|
+
async download(params) {
|
|
2351
|
+
const bucket = params.bucket;
|
|
2352
|
+
const key = params.key;
|
|
2353
|
+
this.logger.debug("Storage DOWNLOAD", { bucket, key });
|
|
2354
|
+
const obj = this.objects.get(this.compositeKey(bucket, key));
|
|
2355
|
+
if (!obj) {
|
|
2356
|
+
throw new Error(`Object not found: ${bucket}/${key}`);
|
|
2357
|
+
}
|
|
2358
|
+
return {
|
|
2359
|
+
content: obj.content,
|
|
2360
|
+
contentType: obj.contentType,
|
|
2361
|
+
size: obj.size,
|
|
2362
|
+
metadata: obj.metadata
|
|
2363
|
+
};
|
|
2364
|
+
}
|
|
2365
|
+
async list(params) {
|
|
2366
|
+
const bucket = params.bucket;
|
|
2367
|
+
const prefix = params.prefix ?? "";
|
|
2368
|
+
const maxKeys = params.maxKeys ?? 1e3;
|
|
2369
|
+
this.logger.debug("Storage LIST", { bucket, prefix, maxKeys });
|
|
2370
|
+
const bucketPrefix = `${bucket}/`;
|
|
2371
|
+
const fullPrefix = `${bucket}/${prefix}`;
|
|
2372
|
+
const results = [];
|
|
2373
|
+
for (const [compositeKey, obj] of this.objects) {
|
|
2374
|
+
if (!compositeKey.startsWith(fullPrefix)) continue;
|
|
2375
|
+
const objectKey = compositeKey.slice(bucketPrefix.length);
|
|
2376
|
+
results.push({
|
|
2377
|
+
key: objectKey,
|
|
2378
|
+
size: obj.size,
|
|
2379
|
+
lastModified: obj.lastModified
|
|
2380
|
+
});
|
|
2381
|
+
}
|
|
2382
|
+
results.sort((a, b) => a.key.localeCompare(b.key));
|
|
2383
|
+
const truncated = results.length > maxKeys;
|
|
2384
|
+
return {
|
|
2385
|
+
keys: results.slice(0, maxKeys),
|
|
2386
|
+
truncated
|
|
2387
|
+
};
|
|
2388
|
+
}
|
|
2389
|
+
async deleteObject(params) {
|
|
2390
|
+
const bucket = params.bucket;
|
|
2391
|
+
const key = params.key;
|
|
2392
|
+
this.logger.debug("Storage DELETE", { bucket, key });
|
|
2393
|
+
const existed = this.objects.has(this.compositeKey(bucket, key));
|
|
2394
|
+
this.objects.delete(this.compositeKey(bucket, key));
|
|
2395
|
+
return { deleted: existed };
|
|
2396
|
+
}
|
|
2397
|
+
async getSignedUrl(params) {
|
|
2398
|
+
const bucket = params.bucket;
|
|
2399
|
+
const key = params.key;
|
|
2400
|
+
const expiresIn = params.expiresIn ?? 3600;
|
|
2401
|
+
const operation = params.operation ?? "get";
|
|
2402
|
+
this.logger.debug("Storage GET_SIGNED_URL", { bucket, key, expiresIn, operation });
|
|
2403
|
+
const expiresAt = Date.now() + expiresIn * 1e3;
|
|
2404
|
+
const token = Math.random().toString(36).slice(2, 18);
|
|
2405
|
+
const url = `https://storage.mock.local/${bucket}/${key}?X-Amz-Algorithm=MOCK-HMAC-SHA256&X-Amz-Expires=${expiresIn}&X-Amz-SignedHeaders=host&X-Amz-Signature=${token}&operation=${operation}`;
|
|
2406
|
+
return { url, expiresAt };
|
|
2407
|
+
}
|
|
2408
|
+
};
|
|
2409
|
+
registerIntegration("storage", StorageIntegration);
|
|
2410
|
+
|
|
2411
|
+
// src/integrations/docker/index.ts
|
|
2412
|
+
var DockerIntegration = class extends BaseIntegration {
|
|
2413
|
+
constructor(config) {
|
|
2414
|
+
super(config);
|
|
2415
|
+
this.containers = /* @__PURE__ */ new Map();
|
|
2416
|
+
this.images = /* @__PURE__ */ new Map();
|
|
2417
|
+
const dockerHost = config.env.DOCKER_HOST;
|
|
2418
|
+
if (dockerHost) {
|
|
2419
|
+
this.logger.warn(
|
|
2420
|
+
"DOCKER_HOST is configured but real Docker client is not yet implemented. Falling back to in-memory simulation.",
|
|
2421
|
+
{ dockerHost }
|
|
2422
|
+
);
|
|
2423
|
+
}
|
|
2424
|
+
this.logger.info("Docker integration initialized (in-memory simulation)");
|
|
2425
|
+
}
|
|
2426
|
+
async execute(action, params) {
|
|
2427
|
+
const validation = this.validateParams(action, params);
|
|
2428
|
+
if (!validation.valid) {
|
|
2429
|
+
return {
|
|
2430
|
+
success: false,
|
|
2431
|
+
error: {
|
|
2432
|
+
name: "IntegrationError",
|
|
2433
|
+
message: "Validation failed",
|
|
2434
|
+
code: "VALIDATION_ERROR",
|
|
2435
|
+
details: validation.errors
|
|
2436
|
+
},
|
|
2437
|
+
metadata: this.createMetadata(action, 0)
|
|
2438
|
+
};
|
|
2439
|
+
}
|
|
2440
|
+
const startTime = Date.now();
|
|
2441
|
+
try {
|
|
2442
|
+
let data;
|
|
2443
|
+
switch (action) {
|
|
2444
|
+
case "build":
|
|
2445
|
+
data = await this.executeWithRetry(() => this.build(params));
|
|
2446
|
+
break;
|
|
2447
|
+
case "run":
|
|
2448
|
+
data = await this.executeWithRetry(() => this.run(params));
|
|
2449
|
+
break;
|
|
2450
|
+
case "stop":
|
|
2451
|
+
data = await this.executeWithRetry(() => this.stop(params));
|
|
2452
|
+
break;
|
|
2453
|
+
case "remove":
|
|
2454
|
+
data = await this.executeWithRetry(() => this.removeContainer(params));
|
|
2455
|
+
break;
|
|
2456
|
+
case "logs":
|
|
2457
|
+
data = await this.executeWithRetry(() => this.logs(params));
|
|
2458
|
+
break;
|
|
2459
|
+
case "status":
|
|
2460
|
+
data = await this.executeWithRetry(() => this.status(params));
|
|
2461
|
+
break;
|
|
2462
|
+
case "list":
|
|
2463
|
+
data = await this.executeWithRetry(() => this.list(params));
|
|
2464
|
+
break;
|
|
2465
|
+
default:
|
|
2466
|
+
throw new Error(`Unknown action: ${action}`);
|
|
2467
|
+
}
|
|
2468
|
+
return {
|
|
2469
|
+
success: true,
|
|
2470
|
+
data,
|
|
2471
|
+
metadata: this.createMetadata(action, Date.now() - startTime)
|
|
2472
|
+
};
|
|
2473
|
+
} catch (error) {
|
|
2474
|
+
return this.handleError(action, error);
|
|
2475
|
+
}
|
|
2476
|
+
}
|
|
2477
|
+
// ---------------------------------------------------------------------------
|
|
2478
|
+
// Helpers
|
|
2479
|
+
// ---------------------------------------------------------------------------
|
|
2480
|
+
/** Generate a random hex container/image ID. */
|
|
2481
|
+
generateId() {
|
|
2482
|
+
const segments = [];
|
|
2483
|
+
for (let i = 0; i < 16; i++) {
|
|
2484
|
+
segments.push(Math.floor(Math.random() * 256).toString(16).padStart(2, "0"));
|
|
2485
|
+
}
|
|
2486
|
+
return segments.join("");
|
|
2487
|
+
}
|
|
2488
|
+
/** Find a container by ID (prefix match supported). */
|
|
2489
|
+
findContainer(containerId) {
|
|
2490
|
+
const exact = this.containers.get(containerId);
|
|
2491
|
+
if (exact) return exact;
|
|
2492
|
+
for (const [id, container] of this.containers) {
|
|
2493
|
+
if (id.startsWith(containerId)) {
|
|
2494
|
+
return container;
|
|
2495
|
+
}
|
|
2496
|
+
}
|
|
2497
|
+
return void 0;
|
|
2498
|
+
}
|
|
2499
|
+
/** Generate realistic log lines for a container. */
|
|
2500
|
+
generateLogLines(container, count) {
|
|
2501
|
+
const lines = [...container.logs];
|
|
2502
|
+
const baseTime = container.startedAt ?? container.createdAt;
|
|
2503
|
+
while (lines.length < count) {
|
|
2504
|
+
const ts = new Date(baseTime + lines.length * 1e3).toISOString();
|
|
2505
|
+
lines.push(`${ts} [info] Container ${container.name} \u2014 heartbeat #${lines.length + 1}`);
|
|
2506
|
+
}
|
|
2507
|
+
return lines;
|
|
2508
|
+
}
|
|
2509
|
+
// ---------------------------------------------------------------------------
|
|
2510
|
+
// Actions
|
|
2511
|
+
// ---------------------------------------------------------------------------
|
|
2512
|
+
async build(params) {
|
|
2513
|
+
const dockerfile = params.dockerfile ?? "Dockerfile";
|
|
2514
|
+
const tag = params.tag;
|
|
2515
|
+
const context = params.context ?? ".";
|
|
2516
|
+
const buildArgs = params.buildArgs ?? {};
|
|
2517
|
+
this.logger.debug("Docker BUILD", { dockerfile, tag, context, buildArgs });
|
|
2518
|
+
const imageId = `sha256:${this.generateId()}`;
|
|
2519
|
+
const size = 15e7 + Math.floor(Math.random() * 35e7);
|
|
2520
|
+
const buildTime = 2e3 + Math.floor(Math.random() * 8e3);
|
|
2521
|
+
const image = {
|
|
2522
|
+
id: imageId,
|
|
2523
|
+
tag,
|
|
2524
|
+
dockerfile,
|
|
2525
|
+
size,
|
|
2526
|
+
createdAt: Date.now()
|
|
2527
|
+
};
|
|
2528
|
+
this.images.set(tag, image);
|
|
2529
|
+
return { imageId, tag, size, buildTime };
|
|
2530
|
+
}
|
|
2531
|
+
async run(params) {
|
|
2532
|
+
const image = params.image;
|
|
2533
|
+
const name = params.name ?? `container-${this.generateId().slice(0, 12)}`;
|
|
2534
|
+
const rawPorts = params.ports ?? [];
|
|
2535
|
+
const env = params.env ?? {};
|
|
2536
|
+
const rawVolumes = params.volumes ?? [];
|
|
2537
|
+
const command = params.command ?? "";
|
|
2538
|
+
this.logger.debug("Docker RUN", { image, name, ports: rawPorts, env, volumes: rawVolumes, command });
|
|
2539
|
+
const containerId = this.generateId();
|
|
2540
|
+
const ports = rawPorts.map((p) => ({
|
|
2541
|
+
host: p.host,
|
|
2542
|
+
container: p.container,
|
|
2543
|
+
protocol: p.protocol ?? "tcp"
|
|
2544
|
+
}));
|
|
2545
|
+
const volumes = rawVolumes.map((v) => ({
|
|
2546
|
+
host: v.host,
|
|
2547
|
+
container: v.container
|
|
2548
|
+
}));
|
|
2549
|
+
const container = {
|
|
2550
|
+
id: containerId,
|
|
2551
|
+
name,
|
|
2552
|
+
image,
|
|
2553
|
+
status: "running",
|
|
2554
|
+
ports,
|
|
2555
|
+
env,
|
|
2556
|
+
volumes,
|
|
2557
|
+
command,
|
|
2558
|
+
labels: {},
|
|
2559
|
+
createdAt: Date.now(),
|
|
2560
|
+
startedAt: Date.now(),
|
|
2561
|
+
stoppedAt: null,
|
|
2562
|
+
logs: [
|
|
2563
|
+
`${(/* @__PURE__ */ new Date()).toISOString()} [info] Starting container ${name} from image ${image}`,
|
|
2564
|
+
`${(/* @__PURE__ */ new Date()).toISOString()} [info] Container ${name} is now running`
|
|
2565
|
+
]
|
|
2566
|
+
};
|
|
2567
|
+
this.containers.set(containerId, container);
|
|
2568
|
+
return {
|
|
2569
|
+
containerId,
|
|
2570
|
+
name,
|
|
2571
|
+
status: container.status,
|
|
2572
|
+
ports
|
|
2573
|
+
};
|
|
2574
|
+
}
|
|
2575
|
+
async stop(params) {
|
|
2576
|
+
const containerId = params.containerId;
|
|
2577
|
+
this.logger.debug("Docker STOP", { containerId });
|
|
2578
|
+
const container = this.findContainer(containerId);
|
|
2579
|
+
if (!container) {
|
|
2580
|
+
throw new Error(`Container not found: ${containerId}`);
|
|
2581
|
+
}
|
|
2582
|
+
if (container.status !== "running" && container.status !== "paused") {
|
|
2583
|
+
throw new Error(`Container ${containerId} is not running (current status: ${container.status})`);
|
|
2584
|
+
}
|
|
2585
|
+
container.status = "exited";
|
|
2586
|
+
container.stoppedAt = Date.now();
|
|
2587
|
+
container.logs.push(
|
|
2588
|
+
`${(/* @__PURE__ */ new Date()).toISOString()} [info] Container ${container.name} stopped`
|
|
2589
|
+
);
|
|
2590
|
+
return {
|
|
2591
|
+
containerId: container.id,
|
|
2592
|
+
status: container.status,
|
|
2593
|
+
stoppedAt: container.stoppedAt
|
|
2594
|
+
};
|
|
2595
|
+
}
|
|
2596
|
+
async removeContainer(params) {
|
|
2597
|
+
const containerId = params.containerId;
|
|
2598
|
+
const force = params.force ?? false;
|
|
2599
|
+
this.logger.debug("Docker REMOVE", { containerId, force });
|
|
2600
|
+
const container = this.findContainer(containerId);
|
|
2601
|
+
if (!container) {
|
|
2602
|
+
throw new Error(`Container not found: ${containerId}`);
|
|
2603
|
+
}
|
|
2604
|
+
if (container.status === "running" && !force) {
|
|
2605
|
+
throw new Error(
|
|
2606
|
+
`Container ${containerId} is running. Stop it first or use force=true.`
|
|
2607
|
+
);
|
|
2608
|
+
}
|
|
2609
|
+
this.containers.delete(container.id);
|
|
2610
|
+
return { containerId: container.id, removed: true };
|
|
2611
|
+
}
|
|
2612
|
+
async logs(params) {
|
|
2613
|
+
const containerId = params.containerId;
|
|
2614
|
+
const tail = params.tail ?? 100;
|
|
2615
|
+
const since = params.since ?? void 0;
|
|
2616
|
+
this.logger.debug("Docker LOGS", { containerId, tail, since });
|
|
2617
|
+
const container = this.findContainer(containerId);
|
|
2618
|
+
if (!container) {
|
|
2619
|
+
throw new Error(`Container not found: ${containerId}`);
|
|
2620
|
+
}
|
|
2621
|
+
let logLines = this.generateLogLines(container, tail);
|
|
2622
|
+
if (since) {
|
|
2623
|
+
const sinceMs = new Date(since).getTime();
|
|
2624
|
+
logLines = logLines.filter((line) => {
|
|
2625
|
+
const timestampMatch = /^\d{4}-\d{2}-\d{2}T[\d:.]+Z/.exec(line);
|
|
2626
|
+
if (!timestampMatch) return true;
|
|
2627
|
+
return new Date(timestampMatch[0]).getTime() >= sinceMs;
|
|
2628
|
+
});
|
|
2629
|
+
}
|
|
2630
|
+
const tailedLines = logLines.slice(-tail);
|
|
2631
|
+
return {
|
|
2632
|
+
containerId: container.id,
|
|
2633
|
+
logs: tailedLines,
|
|
2634
|
+
lineCount: tailedLines.length
|
|
2635
|
+
};
|
|
2636
|
+
}
|
|
2637
|
+
async status(params) {
|
|
2638
|
+
const containerId = params.containerId;
|
|
2639
|
+
this.logger.debug("Docker STATUS", { containerId });
|
|
2640
|
+
const container = this.findContainer(containerId);
|
|
2641
|
+
if (!container) {
|
|
2642
|
+
throw new Error(`Container not found: ${containerId}`);
|
|
2643
|
+
}
|
|
2644
|
+
return {
|
|
2645
|
+
containerId: container.id,
|
|
2646
|
+
name: container.name,
|
|
2647
|
+
image: container.image,
|
|
2648
|
+
status: container.status,
|
|
2649
|
+
ports: container.ports,
|
|
2650
|
+
createdAt: container.createdAt,
|
|
2651
|
+
startedAt: container.startedAt,
|
|
2652
|
+
stoppedAt: container.stoppedAt
|
|
2653
|
+
};
|
|
2654
|
+
}
|
|
2655
|
+
async list(params) {
|
|
2656
|
+
const showAll = params.all ?? false;
|
|
2657
|
+
const filterByLabel = params.filterByLabel ?? void 0;
|
|
2658
|
+
this.logger.debug("Docker LIST", { all: showAll, filterByLabel });
|
|
2659
|
+
let entries = Array.from(this.containers.values());
|
|
2660
|
+
if (!showAll) {
|
|
2661
|
+
entries = entries.filter((c) => c.status === "running");
|
|
2662
|
+
}
|
|
2663
|
+
if (filterByLabel) {
|
|
2664
|
+
const [labelKey, labelValue] = filterByLabel.split("=");
|
|
2665
|
+
entries = entries.filter((c) => {
|
|
2666
|
+
if (labelValue !== void 0) {
|
|
2667
|
+
return c.labels[labelKey] === labelValue;
|
|
2668
|
+
}
|
|
2669
|
+
return labelKey in c.labels;
|
|
2670
|
+
});
|
|
2671
|
+
}
|
|
2672
|
+
entries.sort((a, b) => b.createdAt - a.createdAt);
|
|
2673
|
+
const containers = entries.map((c) => ({
|
|
2674
|
+
id: c.id,
|
|
2675
|
+
name: c.name,
|
|
2676
|
+
image: c.image,
|
|
2677
|
+
status: c.status,
|
|
2678
|
+
ports: c.ports,
|
|
2679
|
+
createdAt: c.createdAt
|
|
2680
|
+
}));
|
|
2681
|
+
return { containers, total: containers.length };
|
|
2682
|
+
}
|
|
2683
|
+
};
|
|
2684
|
+
registerIntegration("docker", DockerIntegration);
|
|
1333
2685
|
|
|
1334
|
-
export { BaseIntegration, ConsoleLogger, DeepAgentIntegration, EmailIntegration, GitHubIntegration, IntegrationFactory, LLMIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, validateParams, withRetry };
|
|
2686
|
+
export { BaseIntegration, CLIIntegration, ConsoleLogger, DeepAgentIntegration, DockerIntegration, EmailIntegration, GitHubIntegration, IntegrationFactory, LLMIntegration, OAuthIntegration, OtelIntegration, QueueIntegration, RedisIntegration, StorageIntegration, StripeIntegration, TwilioIntegration, YouTubeIntegration, getIntegration, getRegisteredIntegrations, isKnownIntegration, registerIntegration, validateParams, withRetry };
|
|
1335
2687
|
//# sourceMappingURL=index.js.map
|
|
1336
2688
|
//# sourceMappingURL=index.js.map
|