@axiom-lattice/gateway 2.1.21 → 2.1.23
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 +18 -0
- package/dist/index.js +444 -14
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +433 -3
- package/dist/index.mjs.map +1 -1
- package/package.json +9 -4
- package/src/controllers/sandbox.ts +334 -0
- package/src/controllers/tools.ts +410 -0
- package/src/index.ts +12 -0
- package/src/routes/index.ts +8 -0
- package/src/schemas/index.ts +42 -0
- package/src/services/agent_service.ts +16 -6
- package/src/services/sandbox_service.ts +217 -0
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
// src/index.ts
|
|
2
2
|
import fastify from "fastify";
|
|
3
3
|
import cors from "@fastify/cors";
|
|
4
|
+
import multipart from "@fastify/multipart";
|
|
4
5
|
import sensible from "@fastify/sensible";
|
|
6
|
+
import websocket from "@fastify/websocket";
|
|
5
7
|
|
|
6
8
|
// src/services/agent_service.ts
|
|
7
9
|
import {
|
|
@@ -46,7 +48,11 @@ async function agent_invoke({
|
|
|
46
48
|
if (!runnable_agent) {
|
|
47
49
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
48
50
|
}
|
|
49
|
-
const runConfig =
|
|
51
|
+
const runConfig = {
|
|
52
|
+
...agentLattice?.config?.runConfig || {},
|
|
53
|
+
assistant_id,
|
|
54
|
+
sandboxConfig: agentLattice?.config?.connectedSandbox
|
|
55
|
+
};
|
|
50
56
|
const result = await runnable_agent.invoke(
|
|
51
57
|
command ? new Command(command) : { ...rest, messages, "x-tenant-id": tenant_id },
|
|
52
58
|
{
|
|
@@ -56,6 +62,7 @@ async function agent_invoke({
|
|
|
56
62
|
"x-tenant-id": tenant_id,
|
|
57
63
|
"x-request-id": run_id,
|
|
58
64
|
"x-thread-id": thread_id,
|
|
65
|
+
"x-assistant-id": assistant_id,
|
|
59
66
|
runConfig
|
|
60
67
|
// Inject runConfig for tools to access
|
|
61
68
|
},
|
|
@@ -88,7 +95,11 @@ async function agent_stream({
|
|
|
88
95
|
messages = [humanMessage];
|
|
89
96
|
}
|
|
90
97
|
const chunkBuffer = getOrCreateChunkBuffer();
|
|
91
|
-
const runConfig =
|
|
98
|
+
const runConfig = {
|
|
99
|
+
...agentLattice?.config?.runConfig || {},
|
|
100
|
+
assistant_id,
|
|
101
|
+
sandboxConfig: agentLattice?.config?.connectedSandbox
|
|
102
|
+
};
|
|
92
103
|
try {
|
|
93
104
|
if (!runnable_agent) {
|
|
94
105
|
throw new Error(`Agent ${assistant_id} not found`);
|
|
@@ -106,6 +117,7 @@ async function agent_stream({
|
|
|
106
117
|
"x-tenant-id": tenant_id,
|
|
107
118
|
"x-request-id": run_id,
|
|
108
119
|
"x-thread-id": thread_id,
|
|
120
|
+
"x-assistant-id": assistant_id,
|
|
109
121
|
runConfig
|
|
110
122
|
// Inject runConfig for tools to access
|
|
111
123
|
},
|
|
@@ -1624,6 +1636,88 @@ async function filterSkillsByLicense(request, reply) {
|
|
|
1624
1636
|
}
|
|
1625
1637
|
}
|
|
1626
1638
|
|
|
1639
|
+
// src/controllers/tools.ts
|
|
1640
|
+
import { getStoreLattice as getStoreLattice4, toolLatticeManager } from "@axiom-lattice/core";
|
|
1641
|
+
function serializeSchema(schema) {
|
|
1642
|
+
if (!schema) {
|
|
1643
|
+
return void 0;
|
|
1644
|
+
}
|
|
1645
|
+
try {
|
|
1646
|
+
if (schema._def) {
|
|
1647
|
+
const def = schema._def;
|
|
1648
|
+
if (def.typeName === "ZodObject") {
|
|
1649
|
+
const shape = def.shape();
|
|
1650
|
+
const properties = {};
|
|
1651
|
+
const required = [];
|
|
1652
|
+
for (const [key, value] of Object.entries(shape)) {
|
|
1653
|
+
const fieldDef = value._def;
|
|
1654
|
+
if (fieldDef) {
|
|
1655
|
+
properties[key] = {
|
|
1656
|
+
type: fieldDef.typeName === "ZodString" ? "string" : fieldDef.typeName === "ZodNumber" ? "number" : fieldDef.typeName === "ZodBoolean" ? "boolean" : fieldDef.typeName === "ZodArray" ? "array" : fieldDef.typeName === "ZodObject" ? "object" : "unknown",
|
|
1657
|
+
description: fieldDef.description
|
|
1658
|
+
};
|
|
1659
|
+
if (!value.isOptional()) {
|
|
1660
|
+
required.push(key);
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
}
|
|
1664
|
+
return {
|
|
1665
|
+
type: "object",
|
|
1666
|
+
properties,
|
|
1667
|
+
required: required.length > 0 ? required : void 0
|
|
1668
|
+
};
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
return {
|
|
1672
|
+
type: "object",
|
|
1673
|
+
description: schema.description || "Schema definition"
|
|
1674
|
+
};
|
|
1675
|
+
} catch (error) {
|
|
1676
|
+
return {
|
|
1677
|
+
type: "object",
|
|
1678
|
+
description: "Schema definition"
|
|
1679
|
+
};
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
async function getToolConfigs(request, reply) {
|
|
1683
|
+
try {
|
|
1684
|
+
const allLattices = toolLatticeManager.getAllLattices();
|
|
1685
|
+
const toolConfigs = allLattices.map((lattice) => {
|
|
1686
|
+
const config = { ...lattice.config };
|
|
1687
|
+
const serializedSchema = config.schema ? serializeSchema(config.schema) : void 0;
|
|
1688
|
+
return {
|
|
1689
|
+
id: lattice.key,
|
|
1690
|
+
name: config.name,
|
|
1691
|
+
description: config.description,
|
|
1692
|
+
schema: serializedSchema,
|
|
1693
|
+
returnDirect: config.returnDirect,
|
|
1694
|
+
needUserApprove: config.needUserApprove
|
|
1695
|
+
};
|
|
1696
|
+
});
|
|
1697
|
+
return reply.send({
|
|
1698
|
+
success: true,
|
|
1699
|
+
message: "Successfully retrieved tool configs",
|
|
1700
|
+
data: {
|
|
1701
|
+
records: toolConfigs,
|
|
1702
|
+
total: toolConfigs.length
|
|
1703
|
+
}
|
|
1704
|
+
});
|
|
1705
|
+
} catch (error) {
|
|
1706
|
+
console.error("Failed to get tool configs", {
|
|
1707
|
+
error: error.message,
|
|
1708
|
+
stack: error.stack
|
|
1709
|
+
});
|
|
1710
|
+
return reply.status(500).send({
|
|
1711
|
+
success: false,
|
|
1712
|
+
message: `Failed to retrieve tool configs: ${error.message}`,
|
|
1713
|
+
data: {
|
|
1714
|
+
records: [],
|
|
1715
|
+
total: 0
|
|
1716
|
+
}
|
|
1717
|
+
});
|
|
1718
|
+
}
|
|
1719
|
+
}
|
|
1720
|
+
|
|
1627
1721
|
// src/schemas/index.ts
|
|
1628
1722
|
var getAllMemoryItemsSchema = {
|
|
1629
1723
|
description: "Get all memory items for an assistant thread",
|
|
@@ -1895,6 +1989,332 @@ var getHealthSchema = {
|
|
|
1895
1989
|
}
|
|
1896
1990
|
};
|
|
1897
1991
|
|
|
1992
|
+
// src/controllers/sandbox.ts
|
|
1993
|
+
import { Readable } from "stream";
|
|
1994
|
+
|
|
1995
|
+
// src/services/sandbox_service.ts
|
|
1996
|
+
import { getAgentConfig, getAgentLattice as getAgentLattice2, getSandBoxManager, normalizeSandboxName } from "@axiom-lattice/core";
|
|
1997
|
+
var ERROR_HTML = `<!DOCTYPE html>
|
|
1998
|
+
<html lang="zh-CN">
|
|
1999
|
+
<head>
|
|
2000
|
+
<meta charset="UTF-8">
|
|
2001
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
2002
|
+
<title>Sandbox \u8FDE\u63A5\u9519\u8BEF</title>
|
|
2003
|
+
<style>
|
|
2004
|
+
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
2005
|
+
body {
|
|
2006
|
+
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
|
|
2007
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2008
|
+
min-height: 100vh;
|
|
2009
|
+
display: flex;
|
|
2010
|
+
align-items: center;
|
|
2011
|
+
justify-content: center;
|
|
2012
|
+
padding: 20px;
|
|
2013
|
+
}
|
|
2014
|
+
.container {
|
|
2015
|
+
background: white;
|
|
2016
|
+
border-radius: 16px;
|
|
2017
|
+
padding: 40px;
|
|
2018
|
+
max-width: 500px;
|
|
2019
|
+
width: 100%;
|
|
2020
|
+
box-shadow: 0 20px 60px rgba(0,0,0,0.3);
|
|
2021
|
+
}
|
|
2022
|
+
.error-icon {
|
|
2023
|
+
width: 80px;
|
|
2024
|
+
height: 80px;
|
|
2025
|
+
background: #fee2e2;
|
|
2026
|
+
border-radius: 50%;
|
|
2027
|
+
display: flex;
|
|
2028
|
+
align-items: center;
|
|
2029
|
+
justify-content: center;
|
|
2030
|
+
margin: 0 auto 24px;
|
|
2031
|
+
}
|
|
2032
|
+
.error-icon svg {
|
|
2033
|
+
width: 40px;
|
|
2034
|
+
height: 40px;
|
|
2035
|
+
color: #dc2626;
|
|
2036
|
+
}
|
|
2037
|
+
h1 { color: #1f2937; margin-bottom: 16px; text-align: center; }
|
|
2038
|
+
p { color: #6b7280; margin-bottom: 12px; line-height: 1.6; }
|
|
2039
|
+
.info {
|
|
2040
|
+
background: #f3f4f6;
|
|
2041
|
+
border-radius: 8px;
|
|
2042
|
+
padding: 16px;
|
|
2043
|
+
margin: 20px 0;
|
|
2044
|
+
}
|
|
2045
|
+
.info-item {
|
|
2046
|
+
display: flex;
|
|
2047
|
+
justify-content: space-between;
|
|
2048
|
+
padding: 8px 0;
|
|
2049
|
+
border-bottom: 1px solid #e5e7eb;
|
|
2050
|
+
}
|
|
2051
|
+
.info-item:last-child { border-bottom: none; }
|
|
2052
|
+
.label { color: #6b7280; font-size: 14px; }
|
|
2053
|
+
.value { color: #1f2937; font-weight: 500; font-family: monospace; }
|
|
2054
|
+
.retry-btn {
|
|
2055
|
+
width: 100%;
|
|
2056
|
+
padding: 14px;
|
|
2057
|
+
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
2058
|
+
color: white;
|
|
2059
|
+
border: none;
|
|
2060
|
+
border-radius: 8px;
|
|
2061
|
+
font-size: 16px;
|
|
2062
|
+
cursor: pointer;
|
|
2063
|
+
transition: transform 0.2s;
|
|
2064
|
+
}
|
|
2065
|
+
.retry-btn:hover { transform: translateY(-2px); }
|
|
2066
|
+
</style>
|
|
2067
|
+
</head>
|
|
2068
|
+
<body>
|
|
2069
|
+
<div class="container">
|
|
2070
|
+
<div class="error-icon">
|
|
2071
|
+
<svg fill="none" stroke="currentColor" viewBox="0 0 24 24">
|
|
2072
|
+
<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"/>
|
|
2073
|
+
</svg>
|
|
2074
|
+
</div>
|
|
2075
|
+
<h1>\u65E0\u6CD5\u8FDE\u63A5\u5230 Sandbox</h1>
|
|
2076
|
+
<p>\u65E0\u6CD5\u8FDE\u63A5\u5230\u6C99\u7BB1\u73AF\u5883\uFF0C\u8BF7\u68C0\u67E5\u914D\u7F6E\u540E\u91CD\u8BD5\u3002</p>
|
|
2077
|
+
<div class="info">
|
|
2078
|
+
<div class="info-item">
|
|
2079
|
+
<span class="label">Assistant ID</span>
|
|
2080
|
+
<span class="value" id="assistantId">-</span>
|
|
2081
|
+
</div>
|
|
2082
|
+
<div class="info-item">
|
|
2083
|
+
<span class="label">Thread ID</span>
|
|
2084
|
+
<span class="value" id="threadId">-</span>
|
|
2085
|
+
</div>
|
|
2086
|
+
<div class="info-item">
|
|
2087
|
+
<span class="label">\u9694\u79BB\u7EA7\u522B</span>
|
|
2088
|
+
<span class="value" id="isolatedLevel">-</span>
|
|
2089
|
+
</div>
|
|
2090
|
+
<div class="info-item">
|
|
2091
|
+
<span class="label">\u9519\u8BEF\u4FE1\u606F</span>
|
|
2092
|
+
<span class="value" id="errorMsg">-</span>
|
|
2093
|
+
</div>
|
|
2094
|
+
</div>
|
|
2095
|
+
<button class="retry-btn" onclick="window.location.reload()">\u91CD\u65B0\u8FDE\u63A5</button>
|
|
2096
|
+
</div>
|
|
2097
|
+
<script>
|
|
2098
|
+
const params = new URLSearchParams(window.location.search);
|
|
2099
|
+
document.getElementById('assistantId').textContent = params.get('assistantId') || '-';
|
|
2100
|
+
document.getElementById('threadId').textContent = params.get('threadId') || '-';
|
|
2101
|
+
document.getElementById('isolatedLevel').textContent = params.get('isolatedLevel') || '-';
|
|
2102
|
+
document.getElementById('errorMsg').textContent = params.get('error') || '\u672A\u77E5\u9519\u8BEF';
|
|
2103
|
+
</script>
|
|
2104
|
+
</body>
|
|
2105
|
+
</html>`;
|
|
2106
|
+
var SandboxService = class {
|
|
2107
|
+
getSandboxConfig(assistantId) {
|
|
2108
|
+
const agentConfig = getAgentConfig(assistantId);
|
|
2109
|
+
if (!agentConfig) {
|
|
2110
|
+
return null;
|
|
2111
|
+
}
|
|
2112
|
+
const agentLattice = getAgentLattice2(assistantId);
|
|
2113
|
+
return agentLattice?.config?.connectedSandbox || null;
|
|
2114
|
+
}
|
|
2115
|
+
computeSandboxName(assistantId, threadId, isolatedLevel) {
|
|
2116
|
+
let sandboxName;
|
|
2117
|
+
switch (isolatedLevel) {
|
|
2118
|
+
case "agent":
|
|
2119
|
+
sandboxName = assistantId;
|
|
2120
|
+
break;
|
|
2121
|
+
case "thread":
|
|
2122
|
+
sandboxName = threadId;
|
|
2123
|
+
break;
|
|
2124
|
+
case "global":
|
|
2125
|
+
default:
|
|
2126
|
+
sandboxName = "global";
|
|
2127
|
+
break;
|
|
2128
|
+
}
|
|
2129
|
+
return normalizeSandboxName(sandboxName);
|
|
2130
|
+
}
|
|
2131
|
+
getTargetUrl(sandboxName) {
|
|
2132
|
+
const sandboxManager = getSandBoxManager("default");
|
|
2133
|
+
return `${sandboxManager.getBaseURL()}/sandbox/${sandboxName}`;
|
|
2134
|
+
}
|
|
2135
|
+
async getVncHtml(sandboxName) {
|
|
2136
|
+
const response = await fetch(`${this.getTargetUrl(sandboxName)}/vnc/index.html`);
|
|
2137
|
+
if (!response.ok) {
|
|
2138
|
+
throw new Error(`Failed to fetch VNC HTML: ${response.statusText}`);
|
|
2139
|
+
}
|
|
2140
|
+
return response.text();
|
|
2141
|
+
}
|
|
2142
|
+
rewriteHtml(html, assistantId, threadId) {
|
|
2143
|
+
const prefix = `/api/assistants/${assistantId}/threads/${threadId}/sandbox/vnc`;
|
|
2144
|
+
let rewritten = html;
|
|
2145
|
+
rewritten = rewritten.replace(
|
|
2146
|
+
/(src|href)=["']([^"']*)["']/g,
|
|
2147
|
+
(match, attr, url) => {
|
|
2148
|
+
if (url.startsWith("http://") || url.startsWith("https://") || url.startsWith("//")) {
|
|
2149
|
+
return match;
|
|
2150
|
+
}
|
|
2151
|
+
const rewrittenUrl = url.startsWith("/") ? `${prefix}${url}` : `${prefix}/${url}`;
|
|
2152
|
+
return `${attr}="${rewrittenUrl}"`;
|
|
2153
|
+
}
|
|
2154
|
+
);
|
|
2155
|
+
rewritten = rewritten.replace(
|
|
2156
|
+
/path=sandbox\/[^&"']+/g,
|
|
2157
|
+
(match) => {
|
|
2158
|
+
return `path=${prefix}/websockify`;
|
|
2159
|
+
}
|
|
2160
|
+
);
|
|
2161
|
+
rewritten = rewritten.replace(
|
|
2162
|
+
/new WebSocket\([^)]*\?path=sandbox[^)]*\)/g,
|
|
2163
|
+
(match) => {
|
|
2164
|
+
return match.replace(/path=sandbox[^)]+/, `path=${prefix}/websockify`);
|
|
2165
|
+
}
|
|
2166
|
+
);
|
|
2167
|
+
return rewritten;
|
|
2168
|
+
}
|
|
2169
|
+
generateErrorHtml(assistantId, threadId, isolatedLevel, errorMessage) {
|
|
2170
|
+
const encodedError = encodeURIComponent(errorMessage);
|
|
2171
|
+
return ERROR_HTML.replace("{assistantId}", assistantId).replace("{threadId}", threadId).replace("{isolatedLevel}", isolatedLevel).replace("{errorMessage}", errorMessage);
|
|
2172
|
+
}
|
|
2173
|
+
};
|
|
2174
|
+
var sandboxService = new SandboxService();
|
|
2175
|
+
|
|
2176
|
+
// src/controllers/sandbox.ts
|
|
2177
|
+
import { getSandBoxManager as getSandBoxManager2 } from "@axiom-lattice/core";
|
|
2178
|
+
function getFilenameFromPath(path) {
|
|
2179
|
+
const segments = path.replace(/\/+$/, "").split("/");
|
|
2180
|
+
return segments[segments.length - 1] || "download";
|
|
2181
|
+
}
|
|
2182
|
+
var EXT_TO_MIME = {
|
|
2183
|
+
".txt": "text/plain",
|
|
2184
|
+
".html": "text/html",
|
|
2185
|
+
".css": "text/css",
|
|
2186
|
+
".js": "application/javascript",
|
|
2187
|
+
".json": "application/json",
|
|
2188
|
+
".pdf": "application/pdf",
|
|
2189
|
+
".png": "image/png",
|
|
2190
|
+
".jpg": "image/jpeg",
|
|
2191
|
+
".jpeg": "image/jpeg",
|
|
2192
|
+
".gif": "image/gif",
|
|
2193
|
+
".webp": "image/webp",
|
|
2194
|
+
".svg": "image/svg+xml",
|
|
2195
|
+
".zip": "application/zip",
|
|
2196
|
+
".csv": "text/csv",
|
|
2197
|
+
".xml": "application/xml"
|
|
2198
|
+
};
|
|
2199
|
+
function getContentTypeFromFilename(filename) {
|
|
2200
|
+
const ext = filename.includes(".") ? filename.slice(filename.lastIndexOf(".")).toLowerCase() : "";
|
|
2201
|
+
return EXT_TO_MIME[ext] ?? "application/octet-stream";
|
|
2202
|
+
}
|
|
2203
|
+
function registerSandboxProxyRoutes(app2) {
|
|
2204
|
+
app2.post(
|
|
2205
|
+
"/api/assistants/:assistantId/threads/:threadId/sandbox/uploadfile",
|
|
2206
|
+
async (request, reply) => {
|
|
2207
|
+
console.log("[Sandbox Upload] Route matched:", request.url);
|
|
2208
|
+
const { assistantId, threadId } = request.params;
|
|
2209
|
+
const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
2210
|
+
if (!sandboxConfig) {
|
|
2211
|
+
return reply.status(500).send({ error: "Assistant sandbox config not found" });
|
|
2212
|
+
}
|
|
2213
|
+
const { isolatedLevel } = sandboxConfig;
|
|
2214
|
+
const sandboxName = sandboxService.computeSandboxName(
|
|
2215
|
+
assistantId,
|
|
2216
|
+
threadId,
|
|
2217
|
+
isolatedLevel
|
|
2218
|
+
);
|
|
2219
|
+
const sandboxManager = getSandBoxManager2("default");
|
|
2220
|
+
const sandbox = await sandboxManager.createSandbox(sandboxName);
|
|
2221
|
+
try {
|
|
2222
|
+
const data = await request.file();
|
|
2223
|
+
if (!data) {
|
|
2224
|
+
return reply.status(400).send({ error: "No file in request" });
|
|
2225
|
+
}
|
|
2226
|
+
const buffer = await data.toBuffer();
|
|
2227
|
+
const pathEntry = data.fields?.path;
|
|
2228
|
+
const pathValue = pathEntry && typeof pathEntry === "object" && "value" in pathEntry ? String(pathEntry.value) : typeof pathEntry === "string" ? pathEntry : void 0;
|
|
2229
|
+
const formData = new FormData();
|
|
2230
|
+
formData.append("file", new Blob([buffer]), data.filename ?? "file");
|
|
2231
|
+
const path = `/home/gem/uploads/${pathValue ? pathValue : ""}${data.filename}`;
|
|
2232
|
+
const uploadResult = await sandbox.file.uploadFile({
|
|
2233
|
+
file: buffer,
|
|
2234
|
+
path
|
|
2235
|
+
});
|
|
2236
|
+
if (!uploadResult.ok) {
|
|
2237
|
+
return reply.status(502).send({ error: `Upload error: ${uploadResult.error}` });
|
|
2238
|
+
}
|
|
2239
|
+
const relativePath = uploadResult.body?.data?.file_path.replace(`/home/gem`, "");
|
|
2240
|
+
const result = { id: relativePath, name: data.filename, size: buffer.length };
|
|
2241
|
+
return reply.status(200).send({ message: "File uploaded successfully", ...result });
|
|
2242
|
+
} catch (error) {
|
|
2243
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2244
|
+
return reply.status(502).send({ error: `Upload proxy error: ${message}` });
|
|
2245
|
+
}
|
|
2246
|
+
}
|
|
2247
|
+
);
|
|
2248
|
+
app2.get(
|
|
2249
|
+
"/api/assistants/:assistantId/threads/:threadId/sandbox/downloadfile",
|
|
2250
|
+
async (request, reply) => {
|
|
2251
|
+
const { assistantId, threadId } = request.params;
|
|
2252
|
+
const { path: filePath } = request.query;
|
|
2253
|
+
if (!filePath || typeof filePath !== "string") {
|
|
2254
|
+
return reply.status(400).send({ error: "Query parameter 'path' is required" });
|
|
2255
|
+
}
|
|
2256
|
+
const sandboxConfig = sandboxService.getSandboxConfig(assistantId);
|
|
2257
|
+
if (!sandboxConfig) {
|
|
2258
|
+
return reply.status(404).send({ error: "Assistant sandbox config not found" });
|
|
2259
|
+
}
|
|
2260
|
+
const { isolatedLevel } = sandboxConfig;
|
|
2261
|
+
const sandboxName = sandboxService.computeSandboxName(
|
|
2262
|
+
assistantId,
|
|
2263
|
+
threadId,
|
|
2264
|
+
isolatedLevel
|
|
2265
|
+
);
|
|
2266
|
+
const sandboxManager = getSandBoxManager2("default");
|
|
2267
|
+
const sandbox = await sandboxManager.createSandbox(sandboxName);
|
|
2268
|
+
try {
|
|
2269
|
+
const resolvedPath = filePath.startsWith("/home/gem") ? filePath : `/home/gem/${filePath.replace(/^\//, "")}`;
|
|
2270
|
+
const filename = getFilenameFromPath(resolvedPath);
|
|
2271
|
+
const inferredContentType = getContentTypeFromFilename(filename);
|
|
2272
|
+
const downloadResult = await sandbox.file.downloadFile({
|
|
2273
|
+
path: resolvedPath
|
|
2274
|
+
});
|
|
2275
|
+
if (!downloadResult.ok) {
|
|
2276
|
+
return reply.status(502).send({
|
|
2277
|
+
error: `Download error: ${JSON.stringify(downloadResult.error)}`
|
|
2278
|
+
});
|
|
2279
|
+
}
|
|
2280
|
+
const body = downloadResult.body;
|
|
2281
|
+
if (typeof body?.stream === "function") {
|
|
2282
|
+
const webStream = body.stream();
|
|
2283
|
+
const nodeStream = Readable.fromWeb(webStream);
|
|
2284
|
+
const contentType2 = body.contentType ?? inferredContentType;
|
|
2285
|
+
const contentDisposition2 = body.contentDisposition ?? `inline; filename="${filename.replace(/"/g, '\\"')}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
2286
|
+
reply = reply.status(200).type(contentType2).header("Content-Disposition", contentDisposition2).send(nodeStream);
|
|
2287
|
+
return reply;
|
|
2288
|
+
}
|
|
2289
|
+
const bodyUnknown = downloadResult.body;
|
|
2290
|
+
let buf;
|
|
2291
|
+
let contentType = inferredContentType;
|
|
2292
|
+
let contentDisposition = `inline; filename="${filename.replace(/"/g, '\\"')}"; filename*=UTF-8''${encodeURIComponent(filename)}`;
|
|
2293
|
+
if (bodyUnknown instanceof ArrayBuffer) {
|
|
2294
|
+
buf = Buffer.from(bodyUnknown);
|
|
2295
|
+
} else if (bodyUnknown instanceof Buffer) {
|
|
2296
|
+
buf = bodyUnknown;
|
|
2297
|
+
} else if (bodyUnknown && typeof bodyUnknown.arrayBuffer === "function") {
|
|
2298
|
+
const res = bodyUnknown;
|
|
2299
|
+
buf = Buffer.from(await res.arrayBuffer());
|
|
2300
|
+
if (res.headers?.get("content-type")) contentType = res.headers.get("content-type");
|
|
2301
|
+
if (res.headers?.get("content-disposition")) contentDisposition = res.headers.get("content-disposition");
|
|
2302
|
+
} else if (bodyUnknown && typeof bodyUnknown.blob === "function") {
|
|
2303
|
+
const blob = await bodyUnknown.blob();
|
|
2304
|
+
buf = Buffer.from(await blob.arrayBuffer());
|
|
2305
|
+
} else {
|
|
2306
|
+
return reply.status(502).send({ error: "Unexpected download response format" });
|
|
2307
|
+
}
|
|
2308
|
+
reply = reply.status(200).type(contentType).header("Content-Disposition", contentDisposition).send(buf);
|
|
2309
|
+
return reply;
|
|
2310
|
+
} catch (error) {
|
|
2311
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
2312
|
+
return reply.status(502).send({ error: `Download proxy error: ${message}` });
|
|
2313
|
+
}
|
|
2314
|
+
}
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
|
|
1898
2318
|
// src/routes/index.ts
|
|
1899
2319
|
var registerLatticeRoutes = (app2) => {
|
|
1900
2320
|
app2.post("/api/runs", createRun);
|
|
@@ -1970,6 +2390,7 @@ var registerLatticeRoutes = (app2) => {
|
|
|
1970
2390
|
);
|
|
1971
2391
|
app2.get("/api/models", getModels);
|
|
1972
2392
|
app2.put("/api/models", updateModels);
|
|
2393
|
+
app2.get("/api/tools", getToolConfigs);
|
|
1973
2394
|
app2.get(
|
|
1974
2395
|
"/health",
|
|
1975
2396
|
{ schema: getHealthSchema },
|
|
@@ -2012,6 +2433,7 @@ var registerLatticeRoutes = (app2) => {
|
|
|
2012
2433
|
"/api/skills/filter/license",
|
|
2013
2434
|
filterSkillsByLicense
|
|
2014
2435
|
);
|
|
2436
|
+
registerSandboxProxyRoutes(app2);
|
|
2015
2437
|
};
|
|
2016
2438
|
|
|
2017
2439
|
// src/swagger.ts
|
|
@@ -2430,12 +2852,20 @@ app.register(cors, {
|
|
|
2430
2852
|
"Authorization",
|
|
2431
2853
|
"X-Requested-With",
|
|
2432
2854
|
"x-tenant-id",
|
|
2433
|
-
"x-request-id"
|
|
2855
|
+
"x-request-id",
|
|
2856
|
+
"x-assistant-id",
|
|
2857
|
+
"x-thread-id"
|
|
2434
2858
|
],
|
|
2435
2859
|
exposedHeaders: ["Content-Type"],
|
|
2436
2860
|
credentials: true
|
|
2437
2861
|
});
|
|
2438
2862
|
app.register(sensible);
|
|
2863
|
+
app.register(multipart, {
|
|
2864
|
+
limits: {
|
|
2865
|
+
fileSize: Number(process.env.BODY_LIMIT) || 50 * 1024 * 1024
|
|
2866
|
+
}
|
|
2867
|
+
});
|
|
2868
|
+
app.register(websocket);
|
|
2439
2869
|
app.setErrorHandler((error, request, reply) => {
|
|
2440
2870
|
const getHeaderValue = (header) => {
|
|
2441
2871
|
if (Array.isArray(header)) {
|