@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/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 = agentLattice?.config?.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 = agentLattice?.config?.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)) {