@axiom-lattice/gateway 2.1.20 → 2.1.22
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/.turbo/turbo-build.log +8 -8
- package/CHANGELOG.md +20 -0
- package/dist/index.js +691 -13
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +680 -2
- package/dist/index.mjs.map +1 -1
- package/package.json +8 -4
- package/src/controllers/sandbox.ts +150 -0
- package/src/controllers/skills.ts +474 -0
- package/src/controllers/tools.ts +410 -0
- package/src/index.ts +4 -0
- package/src/routes/index.ts +69 -1
- package/src/schemas/index.ts +495 -0
- package/src/services/agent_service.ts +14 -6
- package/src/services/sandbox_service.ts +222 -0
package/dist/index.mjs
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import fastify from "fastify";
|
|
3
3
|
import cors from "@fastify/cors";
|
|
4
4
|
import sensible from "@fastify/sensible";
|
|
5
|
+
import websocket from "@fastify/websocket";
|
|
5
6
|
|
|
6
7
|
// src/services/agent_service.ts
|
|
7
8
|
import {
|
|
@@ -46,7 +47,11 @@ async function agent_invoke({
|
|
|
46
47
|
if (!runnable_agent) {
|
|
47
48
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
48
49
|
}
|
|
49
|
-
const runConfig =
|
|
50
|
+
const runConfig = {
|
|
51
|
+
...agentLattice?.config?.runConfig || {},
|
|
52
|
+
assistant_id,
|
|
53
|
+
sandboxConfig: agentLattice?.config?.connectedSandbox
|
|
54
|
+
};
|
|
50
55
|
const result = await runnable_agent.invoke(
|
|
51
56
|
command ? new Command(command) : { ...rest, messages, "x-tenant-id": tenant_id },
|
|
52
57
|
{
|
|
@@ -88,7 +93,11 @@ async function agent_stream({
|
|
|
88
93
|
messages = [humanMessage];
|
|
89
94
|
}
|
|
90
95
|
const chunkBuffer = getOrCreateChunkBuffer();
|
|
91
|
-
const runConfig =
|
|
96
|
+
const runConfig = {
|
|
97
|
+
...agentLattice?.config?.runConfig || {},
|
|
98
|
+
assistant_id,
|
|
99
|
+
sandboxConfig: agentLattice?.config?.connectedSandbox
|
|
100
|
+
};
|
|
92
101
|
try {
|
|
93
102
|
if (!runnable_agent) {
|
|
94
103
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
@@ -1319,6 +1328,393 @@ async function getHealth(request, reply) {
|
|
|
1319
1328
|
}
|
|
1320
1329
|
}
|
|
1321
1330
|
|
|
1331
|
+
// src/controllers/skills.ts
|
|
1332
|
+
import { getStoreLattice as getStoreLattice3 } from "@axiom-lattice/core";
|
|
1333
|
+
import { validateSkillName } from "@axiom-lattice/core";
|
|
1334
|
+
function serializeSkill(skill) {
|
|
1335
|
+
const serialized = {
|
|
1336
|
+
id: skill.id,
|
|
1337
|
+
name: skill.name,
|
|
1338
|
+
description: skill.description,
|
|
1339
|
+
license: skill.license,
|
|
1340
|
+
compatibility: skill.compatibility,
|
|
1341
|
+
metadata: skill.metadata || {},
|
|
1342
|
+
content: skill.content,
|
|
1343
|
+
subSkills: skill.subSkills,
|
|
1344
|
+
createdAt: skill.createdAt instanceof Date ? skill.createdAt.toISOString() : skill.createdAt ? new Date(skill.createdAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString(),
|
|
1345
|
+
updatedAt: skill.updatedAt instanceof Date ? skill.updatedAt.toISOString() : skill.updatedAt ? new Date(skill.updatedAt).toISOString() : (/* @__PURE__ */ new Date()).toISOString()
|
|
1346
|
+
};
|
|
1347
|
+
Object.keys(serialized).forEach((key) => {
|
|
1348
|
+
if (serialized[key] === void 0) {
|
|
1349
|
+
delete serialized[key];
|
|
1350
|
+
}
|
|
1351
|
+
});
|
|
1352
|
+
return serialized;
|
|
1353
|
+
}
|
|
1354
|
+
async function getSkillList(request, reply) {
|
|
1355
|
+
try {
|
|
1356
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1357
|
+
const skillStore = storeLattice.store;
|
|
1358
|
+
const skills = await skillStore.getAllSkills();
|
|
1359
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
1360
|
+
return {
|
|
1361
|
+
success: true,
|
|
1362
|
+
message: "Successfully retrieved skill list",
|
|
1363
|
+
data: {
|
|
1364
|
+
records: serializedSkills,
|
|
1365
|
+
total: serializedSkills.length
|
|
1366
|
+
}
|
|
1367
|
+
};
|
|
1368
|
+
} catch (error) {
|
|
1369
|
+
return reply.status(500).send({
|
|
1370
|
+
success: false,
|
|
1371
|
+
message: `Failed to retrieve skills: ${error.message}`,
|
|
1372
|
+
data: {
|
|
1373
|
+
records: [],
|
|
1374
|
+
total: 0
|
|
1375
|
+
}
|
|
1376
|
+
});
|
|
1377
|
+
}
|
|
1378
|
+
}
|
|
1379
|
+
async function getSkill(request, reply) {
|
|
1380
|
+
try {
|
|
1381
|
+
const { id } = request.params;
|
|
1382
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1383
|
+
const skillStore = storeLattice.store;
|
|
1384
|
+
const skill = await skillStore.getSkillById(id);
|
|
1385
|
+
if (!skill) {
|
|
1386
|
+
return reply.status(404).send({
|
|
1387
|
+
success: false,
|
|
1388
|
+
message: "Skill not found"
|
|
1389
|
+
});
|
|
1390
|
+
}
|
|
1391
|
+
return {
|
|
1392
|
+
success: true,
|
|
1393
|
+
message: "Successfully retrieved skill",
|
|
1394
|
+
data: serializeSkill(skill)
|
|
1395
|
+
};
|
|
1396
|
+
} catch (error) {
|
|
1397
|
+
return reply.status(500).send({
|
|
1398
|
+
success: false,
|
|
1399
|
+
message: `Failed to retrieve skill: ${error.message}`
|
|
1400
|
+
});
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
async function createSkill(request, reply) {
|
|
1404
|
+
try {
|
|
1405
|
+
const data = request.body;
|
|
1406
|
+
if (!data.name) {
|
|
1407
|
+
return reply.status(400).send({
|
|
1408
|
+
success: false,
|
|
1409
|
+
message: "name is required"
|
|
1410
|
+
});
|
|
1411
|
+
}
|
|
1412
|
+
if (!data.description) {
|
|
1413
|
+
return reply.status(400).send({
|
|
1414
|
+
success: false,
|
|
1415
|
+
message: "description is required"
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
try {
|
|
1419
|
+
validateSkillName(data.name);
|
|
1420
|
+
} catch (error) {
|
|
1421
|
+
return reply.status(400).send({
|
|
1422
|
+
success: false,
|
|
1423
|
+
message: error.message || "Invalid skill name format"
|
|
1424
|
+
});
|
|
1425
|
+
}
|
|
1426
|
+
const id = request.body.id || data.name;
|
|
1427
|
+
if (id !== data.name) {
|
|
1428
|
+
return reply.status(400).send({
|
|
1429
|
+
success: false,
|
|
1430
|
+
message: `id "${id}" must equal name "${data.name}" (name is used for path addressing)`
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1434
|
+
const skillStore = storeLattice.store;
|
|
1435
|
+
const exists = await skillStore.hasSkill(id);
|
|
1436
|
+
if (exists) {
|
|
1437
|
+
return reply.status(409).send({
|
|
1438
|
+
success: false,
|
|
1439
|
+
message: `Skill with id "${id}" already exists`
|
|
1440
|
+
});
|
|
1441
|
+
}
|
|
1442
|
+
const newSkill = await skillStore.createSkill(id, data);
|
|
1443
|
+
return reply.status(201).send({
|
|
1444
|
+
success: true,
|
|
1445
|
+
message: "Successfully created skill",
|
|
1446
|
+
data: serializeSkill(newSkill)
|
|
1447
|
+
});
|
|
1448
|
+
} catch (error) {
|
|
1449
|
+
return reply.status(500).send({
|
|
1450
|
+
success: false,
|
|
1451
|
+
message: `Failed to create skill: ${error.message}`
|
|
1452
|
+
});
|
|
1453
|
+
}
|
|
1454
|
+
}
|
|
1455
|
+
async function updateSkill(request, reply) {
|
|
1456
|
+
try {
|
|
1457
|
+
const { id } = request.params;
|
|
1458
|
+
const updates = request.body;
|
|
1459
|
+
if (updates.name !== void 0) {
|
|
1460
|
+
try {
|
|
1461
|
+
validateSkillName(updates.name);
|
|
1462
|
+
} catch (error) {
|
|
1463
|
+
return reply.status(400).send({
|
|
1464
|
+
success: false,
|
|
1465
|
+
message: error.message || "Invalid skill name format"
|
|
1466
|
+
});
|
|
1467
|
+
}
|
|
1468
|
+
}
|
|
1469
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1470
|
+
const skillStore = storeLattice.store;
|
|
1471
|
+
const exists = await skillStore.hasSkill(id);
|
|
1472
|
+
if (!exists) {
|
|
1473
|
+
return reply.status(404).send({
|
|
1474
|
+
success: false,
|
|
1475
|
+
message: "Skill not found"
|
|
1476
|
+
});
|
|
1477
|
+
}
|
|
1478
|
+
const updatedSkill = await skillStore.updateSkill(id, updates);
|
|
1479
|
+
if (!updatedSkill) {
|
|
1480
|
+
return reply.status(500).send({
|
|
1481
|
+
success: false,
|
|
1482
|
+
message: "Failed to update skill"
|
|
1483
|
+
});
|
|
1484
|
+
}
|
|
1485
|
+
return {
|
|
1486
|
+
success: true,
|
|
1487
|
+
message: "Successfully updated skill",
|
|
1488
|
+
data: serializeSkill(updatedSkill)
|
|
1489
|
+
};
|
|
1490
|
+
} catch (error) {
|
|
1491
|
+
return reply.status(500).send({
|
|
1492
|
+
success: false,
|
|
1493
|
+
message: `Failed to update skill: ${error.message}`
|
|
1494
|
+
});
|
|
1495
|
+
}
|
|
1496
|
+
}
|
|
1497
|
+
async function deleteSkill(request, reply) {
|
|
1498
|
+
try {
|
|
1499
|
+
const { id } = request.params;
|
|
1500
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1501
|
+
const skillStore = storeLattice.store;
|
|
1502
|
+
const exists = await skillStore.hasSkill(id);
|
|
1503
|
+
if (!exists) {
|
|
1504
|
+
return reply.status(404).send({
|
|
1505
|
+
success: false,
|
|
1506
|
+
message: "Skill not found"
|
|
1507
|
+
});
|
|
1508
|
+
}
|
|
1509
|
+
const deleted = await skillStore.deleteSkill(id);
|
|
1510
|
+
if (!deleted) {
|
|
1511
|
+
return reply.status(500).send({
|
|
1512
|
+
success: false,
|
|
1513
|
+
message: "Failed to delete skill"
|
|
1514
|
+
});
|
|
1515
|
+
}
|
|
1516
|
+
return {
|
|
1517
|
+
success: true,
|
|
1518
|
+
message: "Successfully deleted skill"
|
|
1519
|
+
};
|
|
1520
|
+
} catch (error) {
|
|
1521
|
+
return reply.status(500).send({
|
|
1522
|
+
success: false,
|
|
1523
|
+
message: `Failed to delete skill: ${error.message}`
|
|
1524
|
+
});
|
|
1525
|
+
}
|
|
1526
|
+
}
|
|
1527
|
+
async function searchSkillsByMetadata(request, reply) {
|
|
1528
|
+
try {
|
|
1529
|
+
const { key, value } = request.query;
|
|
1530
|
+
if (!key || !value) {
|
|
1531
|
+
return reply.status(400).send({
|
|
1532
|
+
success: false,
|
|
1533
|
+
message: "key and value query parameters are required",
|
|
1534
|
+
data: {
|
|
1535
|
+
records: [],
|
|
1536
|
+
total: 0
|
|
1537
|
+
}
|
|
1538
|
+
});
|
|
1539
|
+
}
|
|
1540
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1541
|
+
const skillStore = storeLattice.store;
|
|
1542
|
+
const skills = await skillStore.searchByMetadata(key, value);
|
|
1543
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
1544
|
+
return {
|
|
1545
|
+
success: true,
|
|
1546
|
+
message: "Successfully searched skills",
|
|
1547
|
+
data: {
|
|
1548
|
+
records: serializedSkills,
|
|
1549
|
+
total: serializedSkills.length
|
|
1550
|
+
}
|
|
1551
|
+
};
|
|
1552
|
+
} catch (error) {
|
|
1553
|
+
return reply.status(500).send({
|
|
1554
|
+
success: false,
|
|
1555
|
+
message: `Failed to search skills: ${error.message}`,
|
|
1556
|
+
data: {
|
|
1557
|
+
records: [],
|
|
1558
|
+
total: 0
|
|
1559
|
+
}
|
|
1560
|
+
});
|
|
1561
|
+
}
|
|
1562
|
+
}
|
|
1563
|
+
async function filterSkillsByCompatibility(request, reply) {
|
|
1564
|
+
try {
|
|
1565
|
+
const { compatibility } = request.query;
|
|
1566
|
+
if (!compatibility) {
|
|
1567
|
+
return reply.status(400).send({
|
|
1568
|
+
success: false,
|
|
1569
|
+
message: "compatibility query parameter is required",
|
|
1570
|
+
data: {
|
|
1571
|
+
records: [],
|
|
1572
|
+
total: 0
|
|
1573
|
+
}
|
|
1574
|
+
});
|
|
1575
|
+
}
|
|
1576
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1577
|
+
const skillStore = storeLattice.store;
|
|
1578
|
+
const skills = await skillStore.filterByCompatibility(compatibility);
|
|
1579
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
1580
|
+
return {
|
|
1581
|
+
success: true,
|
|
1582
|
+
message: "Successfully filtered skills",
|
|
1583
|
+
data: {
|
|
1584
|
+
records: serializedSkills,
|
|
1585
|
+
total: serializedSkills.length
|
|
1586
|
+
}
|
|
1587
|
+
};
|
|
1588
|
+
} catch (error) {
|
|
1589
|
+
return reply.status(500).send({
|
|
1590
|
+
success: false,
|
|
1591
|
+
message: `Failed to filter skills: ${error.message}`,
|
|
1592
|
+
data: {
|
|
1593
|
+
records: [],
|
|
1594
|
+
total: 0
|
|
1595
|
+
}
|
|
1596
|
+
});
|
|
1597
|
+
}
|
|
1598
|
+
}
|
|
1599
|
+
async function filterSkillsByLicense(request, reply) {
|
|
1600
|
+
try {
|
|
1601
|
+
const { license } = request.query;
|
|
1602
|
+
if (!license) {
|
|
1603
|
+
return reply.status(400).send({
|
|
1604
|
+
success: false,
|
|
1605
|
+
message: "license query parameter is required",
|
|
1606
|
+
data: {
|
|
1607
|
+
records: [],
|
|
1608
|
+
total: 0
|
|
1609
|
+
}
|
|
1610
|
+
});
|
|
1611
|
+
}
|
|
1612
|
+
const storeLattice = getStoreLattice3("default", "skill");
|
|
1613
|
+
const skillStore = storeLattice.store;
|
|
1614
|
+
const skills = await skillStore.filterByLicense(license);
|
|
1615
|
+
const serializedSkills = skills.map(serializeSkill);
|
|
1616
|
+
return {
|
|
1617
|
+
success: true,
|
|
1618
|
+
message: "Successfully filtered skills",
|
|
1619
|
+
data: {
|
|
1620
|
+
records: serializedSkills,
|
|
1621
|
+
total: serializedSkills.length
|
|
1622
|
+
}
|
|
1623
|
+
};
|
|
1624
|
+
} catch (error) {
|
|
1625
|
+
return reply.status(500).send({
|
|
1626
|
+
success: false,
|
|
1627
|
+
message: `Failed to filter skills: ${error.message}`,
|
|
1628
|
+
data: {
|
|
1629
|
+
records: [],
|
|
1630
|
+
total: 0
|
|
1631
|
+
}
|
|
1632
|
+
});
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
|
|
1636
|
+
// src/controllers/tools.ts
|
|
1637
|
+
import { getStoreLattice as getStoreLattice4, toolLatticeManager } from "@axiom-lattice/core";
|
|
1638
|
+
function serializeSchema(schema) {
|
|
1639
|
+
if (!schema) {
|
|
1640
|
+
return void 0;
|
|
1641
|
+
}
|
|
1642
|
+
try {
|
|
1643
|
+
if (schema._def) {
|
|
1644
|
+
const def = schema._def;
|
|
1645
|
+
if (def.typeName === "ZodObject") {
|
|
1646
|
+
const shape = def.shape();
|
|
1647
|
+
const properties = {};
|
|
1648
|
+
const required = [];
|
|
1649
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
1650
|
+
const fieldDef = value._def;
|
|
1651
|
+
if (fieldDef) {
|
|
1652
|
+
properties[key] = {
|
|
1653
|
+
type: fieldDef.typeName === "ZodString" ? "string" : fieldDef.typeName === "ZodNumber" ? "number" : fieldDef.typeName === "ZodBoolean" ? "boolean" : fieldDef.typeName === "ZodArray" ? "array" : fieldDef.typeName === "ZodObject" ? "object" : "unknown",
|
|
1654
|
+
description: fieldDef.description
|
|
1655
|
+
};
|
|
1656
|
+
if (!value.isOptional()) {
|
|
1657
|
+
required.push(key);
|
|
1658
|
+
}
|
|
1659
|
+
}
|
|
1660
|
+
}
|
|
1661
|
+
return {
|
|
1662
|
+
type: "object",
|
|
1663
|
+
properties,
|
|
1664
|
+
required: required.length > 0 ? required : void 0
|
|
1665
|
+
};
|
|
1666
|
+
}
|
|
1667
|
+
}
|
|
1668
|
+
return {
|
|
1669
|
+
type: "object",
|
|
1670
|
+
description: schema.description || "Schema definition"
|
|
1671
|
+
};
|
|
1672
|
+
} catch (error) {
|
|
1673
|
+
return {
|
|
1674
|
+
type: "object",
|
|
1675
|
+
description: "Schema definition"
|
|
1676
|
+
};
|
|
1677
|
+
}
|
|
1678
|
+
}
|
|
1679
|
+
async function getToolConfigs(request, reply) {
|
|
1680
|
+
try {
|
|
1681
|
+
const allLattices = toolLatticeManager.getAllLattices();
|
|
1682
|
+
const toolConfigs = allLattices.map((lattice) => {
|
|
1683
|
+
const config = { ...lattice.config };
|
|
1684
|
+
const serializedSchema = config.schema ? serializeSchema(config.schema) : void 0;
|
|
1685
|
+
return {
|
|
1686
|
+
id: lattice.key,
|
|
1687
|
+
name: config.name,
|
|
1688
|
+
description: config.description,
|
|
1689
|
+
schema: serializedSchema,
|
|
1690
|
+
returnDirect: config.returnDirect,
|
|
1691
|
+
needUserApprove: config.needUserApprove
|
|
1692
|
+
};
|
|
1693
|
+
});
|
|
1694
|
+
return reply.send({
|
|
1695
|
+
success: true,
|
|
1696
|
+
message: "Successfully retrieved tool configs",
|
|
1697
|
+
data: {
|
|
1698
|
+
records: toolConfigs,
|
|
1699
|
+
total: toolConfigs.length
|
|
1700
|
+
}
|
|
1701
|
+
});
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
console.error("Failed to get tool configs", {
|
|
1704
|
+
error: error.message,
|
|
1705
|
+
stack: error.stack
|
|
1706
|
+
});
|
|
1707
|
+
return reply.status(500).send({
|
|
1708
|
+
success: false,
|
|
1709
|
+
message: `Failed to retrieve tool configs: ${error.message}`,
|
|
1710
|
+
data: {
|
|
1711
|
+
records: [],
|
|
1712
|
+
total: 0
|
|
1713
|
+
}
|
|
1714
|
+
});
|
|
1715
|
+
}
|
|
1716
|
+
}
|
|
1717
|
+
|
|
1322
1718
|
// src/schemas/index.ts
|
|
1323
1719
|
var getAllMemoryItemsSchema = {
|
|
1324
1720
|
description: "Get all memory items for an assistant thread",
|
|
@@ -1590,6 +1986,256 @@ var getHealthSchema = {
|
|
|
1590
1986
|
}
|
|
1591
1987
|
};
|
|
1592
1988
|
|
|
1989
|
+
// src/services/sandbox_service.ts
|
|
1990
|
+
import { getAgentConfig, getAgentLattice as getAgentLattice2, normalizeSandboxName } from "@axiom-lattice/core";
|
|
1991
|
+
var SANDBOX_BASE_URL = process.env.SANDBOX_BASE_URL || "http://localhost:8080";
|
|
1992
|
+
var ERROR_HTML = `<!DOCTYPE html>
|
|
1993
|
+
<html lang="zh-CN">
|
|
1994
|
+
<head>
|
|
1995
|
+
<meta charset="UTF-8">
|
|
1996
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
1997
|
+
<title>Sandbox \u8FDE\u63A5\u9519\u8BEF</title>
|
|
1998
|
+
<style>
|
|
1999
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2000
|
+
body {
|
|
2001
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2002
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2003
|
+
min-height: 100vh;
|
|
2004
|
+
display: flex;
|
|
2005
|
+
align-items: center;
|
|
2006
|
+
justify-content: center;
|
|
2007
|
+
padding: 20px;
|
|
2008
|
+
}
|
|
2009
|
+
.container {
|
|
2010
|
+
background: white;
|
|
2011
|
+
border-radius: 16px;
|
|
2012
|
+
padding: 40px;
|
|
2013
|
+
max-width: 500px;
|
|
2014
|
+
width: 100%;
|
|
2015
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
2016
|
+
}
|
|
2017
|
+
.error-icon {
|
|
2018
|
+
width: 80px;
|
|
2019
|
+
height: 80px;
|
|
2020
|
+
background: #fee2e2;
|
|
2021
|
+
border-radius: 50%;
|
|
2022
|
+
display: flex;
|
|
2023
|
+
align-items: center;
|
|
2024
|
+
justify-content: center;
|
|
2025
|
+
margin: 0 auto 24px;
|
|
2026
|
+
}
|
|
2027
|
+
.error-icon svg {
|
|
2028
|
+
width: 40px;
|
|
2029
|
+
height: 40px;
|
|
2030
|
+
color: #dc2626;
|
|
2031
|
+
}
|
|
2032
|
+
h1 { color: #1f2937; margin-bottom: 16px; text-align: center; }
|
|
2033
|
+
p { color: #6b7280; margin-bottom: 12px; line-height: 1.6; }
|
|
2034
|
+
.info {
|
|
2035
|
+
background: #f3f4f6;
|
|
2036
|
+
border-radius: 8px;
|
|
2037
|
+
padding: 16px;
|
|
2038
|
+
margin: 20px 0;
|
|
2039
|
+
}
|
|
2040
|
+
.info-item {
|
|
2041
|
+
display: flex;
|
|
2042
|
+
justify-content: space-between;
|
|
2043
|
+
padding: 8px 0;
|
|
2044
|
+
border-bottom: 1px solid #e5e7eb;
|
|
2045
|
+
}
|
|
2046
|
+
.info-item:last-child { border-bottom: none; }
|
|
2047
|
+
.label { color: #6b7280; font-size: 14px; }
|
|
2048
|
+
.value { color: #1f2937; font-weight: 500; font-family: monospace; }
|
|
2049
|
+
.retry-btn {
|
|
2050
|
+
width: 100%;
|
|
2051
|
+
padding: 14px;
|
|
2052
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2053
|
+
color: white;
|
|
2054
|
+
border: none;
|
|
2055
|
+
border-radius: 8px;
|
|
2056
|
+
font-size: 16px;
|
|
2057
|
+
cursor: pointer;
|
|
2058
|
+
transition: transform 0.2s;
|
|
2059
|
+
}
|
|
2060
|
+
.retry-btn:hover { transform: translateY(-2px); }
|
|
2061
|
+
</style>
|
|
2062
|
+
</head>
|
|
2063
|
+
<body>
|
|
2064
|
+
<div class="container">
|
|
2065
|
+
<div class="error-icon">
|
|
2066
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2067
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"/>
|
|
2068
|
+
</svg>
|
|
2069
|
+
</div>
|
|
2070
|
+
<h1>\u65E0\u6CD5\u8FDE\u63A5\u5230 Sandbox</h1>
|
|
2071
|
+
<p>\u65E0\u6CD5\u8FDE\u63A5\u5230\u6C99\u7BB1\u73AF\u5883\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E\u540E\u91CD\u8BD5\u3002</p>
|
|
2072
|
+
<div class="info">
|
|
2073
|
+
<div class="info-item">
|
|
2074
|
+
<span class="label">Assistant ID</span>
|
|
2075
|
+
<span class="value" id="assistantId">-</span>
|
|
2076
|
+
</div>
|
|
2077
|
+
<div class="info-item">
|
|
2078
|
+
<span class="label">Thread ID</span>
|
|
2079
|
+
<span class="value" id="threadId">-</span>
|
|
2080
|
+
</div>
|
|
2081
|
+
<div class="info-item">
|
|
2082
|
+
<span class="label">\u9694\u79BB\u7EA7\u522B</span>
|
|
2083
|
+
<span class="value" id="isolatedLevel">-</span>
|
|
2084
|
+
</div>
|
|
2085
|
+
<div class="info-item">
|
|
2086
|
+
<span class="label">\u9519\u8BEF\u4FE1\u606F</span>
|
|
2087
|
+
<span class="value" id="errorMsg">-</span>
|
|
2088
|
+
</div>
|
|
2089
|
+
</div>
|
|
2090
|
+
<button class="retry-btn" onclick="window.location.reload()">\u91CD\u65B0\u8FDE\u63A5</button>
|
|
2091
|
+
</div>
|
|
2092
|
+
<script>
|
|
2093
|
+
const params = new URLSearchParams(window.location.search);
|
|
2094
|
+
document.getElementById('assistantId').textContent = params.get('assistantId') || '-';
|
|
2095
|
+
document.getElementById('threadId').textContent = params.get('threadId') || '-';
|
|
2096
|
+
document.getElementById('isolatedLevel').textContent = params.get('isolatedLevel') || '-';
|
|
2097
|
+
document.getElementById('errorMsg').textContent = params.get('error') || '\u672A\u77E5\u9519\u8BEF';
|
|
2098
|
+
</script>
|
|
2099
|
+
</body>
|
|
2100
|
+
</html>`;
|
|
2101
|
+
var SandboxService = class {
|
|
2102
|
+
constructor(baseUrl) {
|
|
2103
|
+
this.baseUrl = baseUrl || SANDBOX_BASE_URL;
|
|
2104
|
+
}
|
|
2105
|
+
getSandboxConfig(assistantId) {
|
|
2106
|
+
const agentConfig = getAgentConfig(assistantId);
|
|
2107
|
+
if (!agentConfig) {
|
|
2108
|
+
return null;
|
|
2109
|
+
}
|
|
2110
|
+
const agentLattice = getAgentLattice2(assistantId);
|
|
2111
|
+
return agentLattice?.config?.connectedSandbox || null;
|
|
2112
|
+
}
|
|
2113
|
+
computeSandboxName(assistantId, threadId, isolatedLevel) {
|
|
2114
|
+
let sandboxName;
|
|
2115
|
+
switch (isolatedLevel) {
|
|
2116
|
+
case "agent":
|
|
2117
|
+
sandboxName = assistantId;
|
|
2118
|
+
break;
|
|
2119
|
+
case "thread":
|
|
2120
|
+
sandboxName = threadId;
|
|
2121
|
+
break;
|
|
2122
|
+
case "global":
|
|
2123
|
+
default:
|
|
2124
|
+
sandboxName = "global";
|
|
2125
|
+
break;
|
|
2126
|
+
}
|
|
2127
|
+
return normalizeSandboxName(sandboxName);
|
|
2128
|
+
}
|
|
2129
|
+
getTargetUrl(sandboxName) {
|
|
2130
|
+
return `${this.baseUrl}/sandbox/${sandboxName}`;
|
|
2131
|
+
}
|
|
2132
|
+
async getVncHtml(sandboxName) {
|
|
2133
|
+
const response = await fetch(`${this.getTargetUrl(sandboxName)}/vnc/index.html`);
|
|
2134
|
+
if (!response.ok) {
|
|
2135
|
+
throw new Error(`Failed to fetch VNC HTML: ${response.statusText}`);
|
|
2136
|
+
}
|
|
2137
|
+
return response.text();
|
|
2138
|
+
}
|
|
2139
|
+
rewriteHtml(html, assistantId, threadId) {
|
|
2140
|
+
const prefix = `/api/assistants/${assistantId}/threads/${threadId}/sandbox/vnc`;
|
|
2141
|
+
let rewritten = html;
|
|
2142
|
+
rewritten = rewritten.replace(
|
|
2143
|
+
/(src|href)=["']([^"']*)["']/g,
|
|
2144
|
+
(match, attr, url) => {
|
|
2145
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
|
|
2146
|
+
return match;
|
|
2147
|
+
}
|
|
2148
|
+
const rewrittenUrl = url.startsWith("/") ? `${prefix}${url}` : `${prefix}/${url}`;
|
|
2149
|
+
return `${attr}="${rewrittenUrl}"`;
|
|
2150
|
+
}
|
|
2151
|
+
);
|
|
2152
|
+
rewritten = rewritten.replace(
|
|
2153
|
+
/path=sandbox\/[^&"']+/g,
|
|
2154
|
+
(match) => {
|
|
2155
|
+
return `path=${prefix}/websockify`;
|
|
2156
|
+
}
|
|
2157
|
+
);
|
|
2158
|
+
rewritten = rewritten.replace(
|
|
2159
|
+
/new WebSocket\([^)]*\?path=sandbox[^)]*\)/g,
|
|
2160
|
+
(match) => {
|
|
2161
|
+
return match.replace(/path=sandbox[^)]+/, `path=${prefix}/websockify`);
|
|
2162
|
+
}
|
|
2163
|
+
);
|
|
2164
|
+
return rewritten;
|
|
2165
|
+
}
|
|
2166
|
+
generateErrorHtml(assistantId, threadId, isolatedLevel, errorMessage) {
|
|
2167
|
+
const encodedError = encodeURIComponent(errorMessage);
|
|
2168
|
+
return ERROR_HTML.replace("{assistantId}", assistantId).replace("{threadId}", threadId).replace("{isolatedLevel}", isolatedLevel).replace("{errorMessage}", errorMessage);
|
|
2169
|
+
}
|
|
2170
|
+
};
|
|
2171
|
+
var sandboxService = new SandboxService();
|
|
2172
|
+
|
|
2173
|
+
// src/controllers/sandbox.ts
|
|
2174
|
+
var SANDBOX_BASE_URL2 = process.env.SANDBOX_BASE_URL || "http://localhost:8080";
|
|
2175
|
+
async function registerSandboxProxyRoutes(app2) {
|
|
2176
|
+
app2.get(
|
|
2177
|
+
"/api/assistants/:assistantId/threads/:threadId/sandbox",
|
|
2178
|
+
async (request, reply) => {
|
|
2179
|
+
const { assistantId, threadId } = request.params;
|
|
2180
|
+
const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
2181
|
+
if (!sandboxConfig) {
|
|
2182
|
+
const errorHtml = sandboxService.generateErrorHtml(
|
|
2183
|
+
assistantId,
|
|
2184
|
+
threadId,
|
|
2185
|
+
"unknown",
|
|
2186
|
+
`Assistant ${assistantId} not found`
|
|
2187
|
+
);
|
|
2188
|
+
return reply.status(404).type("text/html").send(errorHtml);
|
|
2189
|
+
}
|
|
2190
|
+
const { isolatedLevel } = sandboxConfig;
|
|
2191
|
+
const sandboxName = sandboxService.computeSandboxName(
|
|
2192
|
+
assistantId,
|
|
2193
|
+
threadId,
|
|
2194
|
+
isolatedLevel
|
|
2195
|
+
);
|
|
2196
|
+
try {
|
|
2197
|
+
const html = await sandboxService.getVncHtml(sandboxName);
|
|
2198
|
+
const rewrittenHtml = sandboxService.rewriteHtml(html, assistantId, threadId);
|
|
2199
|
+
return reply.type("text/html").send(rewrittenHtml);
|
|
2200
|
+
} catch (error) {
|
|
2201
|
+
const errorHtml = sandboxService.generateErrorHtml(
|
|
2202
|
+
assistantId,
|
|
2203
|
+
threadId,
|
|
2204
|
+
isolatedLevel,
|
|
2205
|
+
error.message || "Failed to connect to sandbox"
|
|
2206
|
+
);
|
|
2207
|
+
return reply.status(502).type("text/html").send(errorHtml);
|
|
2208
|
+
}
|
|
2209
|
+
}
|
|
2210
|
+
);
|
|
2211
|
+
app2.get(
|
|
2212
|
+
"/api/assistants/:assistantId/threads/:threadId/sandbox/vnc/*",
|
|
2213
|
+
async (request, reply) => {
|
|
2214
|
+
const { assistantId, threadId, "*": restPath } = request.params;
|
|
2215
|
+
const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
2216
|
+
if (!sandboxConfig) {
|
|
2217
|
+
return reply.status(404).send("Assistant not found");
|
|
2218
|
+
}
|
|
2219
|
+
const { isolatedLevel } = sandboxConfig;
|
|
2220
|
+
const sandboxName = sandboxService.computeSandboxName(
|
|
2221
|
+
assistantId,
|
|
2222
|
+
threadId,
|
|
2223
|
+
isolatedLevel
|
|
2224
|
+
);
|
|
2225
|
+
const targetPath = restPath ? `/vnc/${restPath}` : "/vnc/";
|
|
2226
|
+
const targetUrl = `${sandboxService.getTargetUrl(sandboxName)}${targetPath}`;
|
|
2227
|
+
try {
|
|
2228
|
+
const response = await fetch(targetUrl);
|
|
2229
|
+
const contentType = response.headers.get("content-type") || "application/octet-stream";
|
|
2230
|
+
const body = await response.arrayBuffer();
|
|
2231
|
+
reply.status(response.status).type(contentType).send(Buffer.from(body));
|
|
2232
|
+
} catch (error) {
|
|
2233
|
+
reply.status(502).send(`Proxy error: ${error.message}`);
|
|
2234
|
+
}
|
|
2235
|
+
}
|
|
2236
|
+
);
|
|
2237
|
+
}
|
|
2238
|
+
|
|
1593
2239
|
// src/routes/index.ts
|
|
1594
2240
|
var registerLatticeRoutes = (app2) => {
|
|
1595
2241
|
app2.post("/api/runs", createRun);
|
|
@@ -1665,6 +2311,7 @@ var registerLatticeRoutes = (app2) => {
|
|
|
1665
2311
|
);
|
|
1666
2312
|
app2.get("/api/models", getModels);
|
|
1667
2313
|
app2.put("/api/models", updateModels);
|
|
2314
|
+
app2.get("/api/tools", getToolConfigs);
|
|
1668
2315
|
app2.get(
|
|
1669
2316
|
"/health",
|
|
1670
2317
|
{ schema: getHealthSchema },
|
|
@@ -1678,6 +2325,36 @@ var registerLatticeRoutes = (app2) => {
|
|
|
1678
2325
|
app2.post("/api/schedules/:taskId/cancel", cancelScheduledTask);
|
|
1679
2326
|
app2.post("/api/schedules/:taskId/pause", pauseScheduledTask);
|
|
1680
2327
|
app2.post("/api/schedules/:taskId/resume", resumeScheduledTask);
|
|
2328
|
+
app2.get("/api/skills", getSkillList);
|
|
2329
|
+
app2.get(
|
|
2330
|
+
"/api/skills/:id",
|
|
2331
|
+
getSkill
|
|
2332
|
+
);
|
|
2333
|
+
app2.post(
|
|
2334
|
+
"/api/skills",
|
|
2335
|
+
createSkill
|
|
2336
|
+
);
|
|
2337
|
+
app2.put(
|
|
2338
|
+
"/api/skills/:id",
|
|
2339
|
+
updateSkill
|
|
2340
|
+
);
|
|
2341
|
+
app2.delete(
|
|
2342
|
+
"/api/skills/:id",
|
|
2343
|
+
deleteSkill
|
|
2344
|
+
);
|
|
2345
|
+
app2.get(
|
|
2346
|
+
"/api/skills/search/metadata",
|
|
2347
|
+
searchSkillsByMetadata
|
|
2348
|
+
);
|
|
2349
|
+
app2.get(
|
|
2350
|
+
"/api/skills/filter/compatibility",
|
|
2351
|
+
filterSkillsByCompatibility
|
|
2352
|
+
);
|
|
2353
|
+
app2.get(
|
|
2354
|
+
"/api/skills/filter/license",
|
|
2355
|
+
filterSkillsByLicense
|
|
2356
|
+
);
|
|
2357
|
+
registerSandboxProxyRoutes(app2);
|
|
1681
2358
|
};
|
|
1682
2359
|
|
|
1683
2360
|
// src/swagger.ts
|
|
@@ -2102,6 +2779,7 @@ app.register(cors, {
|
|
|
2102
2779
|
credentials: true
|
|
2103
2780
|
});
|
|
2104
2781
|
app.register(sensible);
|
|
2782
|
+
app.register(websocket);
|
|
2105
2783
|
app.setErrorHandler((error, request, reply) => {
|
|
2106
2784
|
const getHeaderValue = (header) => {
|
|
2107
2785
|
if (Array.isArray(header)) {
|