@truefoundry/trueforge 0.1.2 → 0.1.4-rc.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/dist/_frontend/assets/{ConnectorSettings-XEO5ZQO5-BXis0tQK.js → ConnectorSettings-ODC3LVHN-jhiXpbVc.js} +1 -1
- package/dist/_frontend/assets/ConnectorSettings-ODC3LVHN-jhiXpbVc.js.br +0 -0
- package/dist/_frontend/assets/ConnectorSettings-ODC3LVHN-jhiXpbVc.js.gz +0 -0
- package/dist/_frontend/assets/{DockLayout-F2KYSY4U-D8P84kCr.js → DockLayout-2RWDSUKE-DY3y3Is3.js} +1 -1
- package/dist/_frontend/assets/{DrawerLayout-FJ7XC2FU-ChH-Qykr.js → DrawerLayout-CZLJYMIN-BYncSNoi.js} +2 -2
- package/dist/_frontend/assets/DrawerLayout-CZLJYMIN-BYncSNoi.js.br +0 -0
- package/dist/_frontend/assets/DrawerLayout-CZLJYMIN-BYncSNoi.js.gz +0 -0
- package/dist/_frontend/assets/{ModelSettings-2QB7LE22-CDZku4g3.js → ModelSettings-CKQ5CVAC-DxQtYKup.js} +1 -1
- package/dist/_frontend/assets/ModelSettings-CKQ5CVAC-DxQtYKup.js.br +0 -0
- package/dist/_frontend/assets/ModelSettings-CKQ5CVAC-DxQtYKup.js.gz +0 -0
- package/dist/_frontend/assets/{SandboxSettings-QDDIPRBP-CImJLa0r.js → SandboxSettings-HSCPYUOC-DMFf-U04.js} +1 -1
- package/dist/_frontend/assets/SandboxSettings-HSCPYUOC-DMFf-U04.js.br +0 -0
- package/dist/_frontend/assets/SandboxSettings-HSCPYUOC-DMFf-U04.js.gz +0 -0
- package/dist/_frontend/assets/SettingsBuilder-MYWRFXRI-C4p8uaXT.js +2 -0
- package/dist/_frontend/assets/SettingsBuilder-MYWRFXRI-C4p8uaXT.js.br +0 -0
- package/dist/_frontend/assets/SettingsBuilder-MYWRFXRI-C4p8uaXT.js.gz +0 -0
- package/dist/_frontend/assets/{ShellRouteSync-T4WLY57Q-CetHx5EA.js → ShellRouteSync-T4WLY57Q-BC8a0ojU.js} +1 -1
- package/dist/_frontend/assets/ShellRouteSync-T4WLY57Q-BC8a0ojU.js.br +0 -0
- package/dist/_frontend/assets/ShellRouteSync-T4WLY57Q-BC8a0ojU.js.gz +0 -0
- package/dist/_frontend/assets/{SidebarLayout-KX6RSJTP-B8G4CTTb.js → SidebarLayout-WVY54TGZ-Ds7NVk-f.js} +2 -2
- package/dist/_frontend/assets/SidebarLayout-WVY54TGZ-Ds7NVk-f.js.br +0 -0
- package/dist/_frontend/assets/SidebarLayout-WVY54TGZ-Ds7NVk-f.js.gz +0 -0
- package/dist/_frontend/assets/{SkillSettings-5EETEEDI-DbJE0Ms2.js → SkillSettings-ECH6SHZ7-BtdCmc6B.js} +1 -1
- package/dist/_frontend/assets/SkillSettings-ECH6SHZ7-BtdCmc6B.js.br +0 -0
- package/dist/_frontend/assets/SkillSettings-ECH6SHZ7-BtdCmc6B.js.gz +0 -0
- package/dist/_frontend/assets/{TrueForgeUIWithRouter-IAQQTD5V-9G1rELmh.js → TrueForgeUIWithRouter-RKZ2E7PV-vF7U7cGf.js} +1 -1
- package/dist/_frontend/assets/{WidgetLayout-S6EK62TZ-BPf7T3wa.js → WidgetLayout-NWWB2SXI-DJtuM33Y.js} +1 -1
- package/dist/_frontend/assets/WidgetLayout-NWWB2SXI-DJtuM33Y.js.br +0 -0
- package/dist/_frontend/assets/WidgetLayout-NWWB2SXI-DJtuM33Y.js.gz +0 -0
- package/dist/_frontend/assets/{chunk-62JRHF6Z-BUuLA42_.js → chunk-62JRHF6Z-TG4aaXok.js} +1 -1
- package/dist/_frontend/assets/chunk-62JRHF6Z-TG4aaXok.js.br +0 -0
- package/dist/_frontend/assets/chunk-62JRHF6Z-TG4aaXok.js.gz +0 -0
- package/dist/_frontend/assets/{chunk-X6U2Q7X5-DDlfUle1.js → chunk-OZTNBS6V-axorP9qO.js} +1 -1
- package/dist/_frontend/assets/chunk-OZTNBS6V-axorP9qO.js.br +0 -0
- package/dist/_frontend/assets/chunk-OZTNBS6V-axorP9qO.js.gz +0 -0
- package/dist/_frontend/assets/{chunk-PTYEOSHT-C4gdhLwo.js → chunk-TCAVLWYL-BXtNAtCy.js} +2 -2
- package/dist/_frontend/assets/chunk-TCAVLWYL-BXtNAtCy.js.br +0 -0
- package/dist/_frontend/assets/chunk-TCAVLWYL-BXtNAtCy.js.gz +0 -0
- package/dist/_frontend/assets/{cssMode-C1whwlDW.js → cssMode-yEIs6GIw.js} +1 -1
- package/dist/_frontend/assets/cssMode-yEIs6GIw.js.br +0 -0
- package/dist/_frontend/assets/cssMode-yEIs6GIw.js.gz +0 -0
- package/dist/_frontend/assets/{freemarker2-CaMIS9bY.js → freemarker2-aDGnYU8i.js} +1 -1
- package/dist/_frontend/assets/freemarker2-aDGnYU8i.js.br +0 -0
- package/dist/_frontend/assets/freemarker2-aDGnYU8i.js.gz +0 -0
- package/dist/_frontend/assets/{handlebars-Donx2d9H.js → handlebars-B6ny31dN.js} +1 -1
- package/dist/_frontend/assets/handlebars-B6ny31dN.js.br +0 -0
- package/dist/_frontend/assets/handlebars-B6ny31dN.js.gz +0 -0
- package/dist/_frontend/assets/{html-CtI8kZ6e.js → html-D7u-MYoP.js} +1 -1
- package/dist/_frontend/assets/html-D7u-MYoP.js.br +0 -0
- package/dist/_frontend/assets/html-D7u-MYoP.js.gz +0 -0
- package/dist/_frontend/assets/{htmlMode-B6ky8VkF.js → htmlMode-kNcFDXKs.js} +1 -1
- package/dist/_frontend/assets/htmlMode-kNcFDXKs.js.br +0 -0
- package/dist/_frontend/assets/htmlMode-kNcFDXKs.js.gz +0 -0
- package/dist/_frontend/assets/{index-DFmgzNuj.js → index-CLJuaUhx.js} +3 -3
- package/dist/_frontend/assets/index-CLJuaUhx.js.br +0 -0
- package/dist/_frontend/assets/index-CLJuaUhx.js.gz +0 -0
- package/dist/_frontend/assets/{index-DWr7ad72.js → index-D1GDH6ia.js} +122 -122
- package/dist/_frontend/assets/index-D1GDH6ia.js.br +0 -0
- package/dist/_frontend/assets/index-D1GDH6ia.js.gz +0 -0
- package/dist/_frontend/assets/{javascript-BDngdIws.js → javascript-BLSK7s1P.js} +1 -1
- package/dist/_frontend/assets/{jsonMode-B7OogXWW.js → jsonMode-XHJKWC_S.js} +1 -1
- package/dist/_frontend/assets/jsonMode-XHJKWC_S.js.br +0 -0
- package/dist/_frontend/assets/jsonMode-XHJKWC_S.js.gz +0 -0
- package/dist/_frontend/assets/{liquid-D_8RkuUW.js → liquid-Db1WZdZB.js} +1 -1
- package/dist/_frontend/assets/liquid-Db1WZdZB.js.br +0 -0
- package/dist/_frontend/assets/liquid-Db1WZdZB.js.gz +0 -0
- package/dist/_frontend/assets/{lspLanguageFeatures-DJf8_W2N.js → lspLanguageFeatures-BFyBtl8M.js} +1 -1
- package/dist/_frontend/assets/lspLanguageFeatures-BFyBtl8M.js.br +0 -0
- package/dist/_frontend/assets/lspLanguageFeatures-BFyBtl8M.js.gz +0 -0
- package/dist/_frontend/assets/{mdx-HcpZSZ2w.js → mdx-DtFWu4ex.js} +1 -1
- package/dist/_frontend/assets/mdx-DtFWu4ex.js.br +0 -0
- package/dist/_frontend/assets/mdx-DtFWu4ex.js.gz +0 -0
- package/dist/_frontend/assets/{python-CPiT1vB4.js → python-2R7BOup6.js} +1 -1
- package/dist/_frontend/assets/python-2R7BOup6.js.br +0 -0
- package/dist/_frontend/assets/python-2R7BOup6.js.gz +0 -0
- package/dist/_frontend/assets/{razor-oizHS-vq.js → razor-DQLxdj72.js} +1 -1
- package/dist/_frontend/assets/razor-DQLxdj72.js.br +0 -0
- package/dist/_frontend/assets/razor-DQLxdj72.js.gz +0 -0
- package/dist/_frontend/assets/{tsMode-n5kkpKJX.js → tsMode-Ctn4PuNt.js} +1 -1
- package/dist/_frontend/assets/tsMode-Ctn4PuNt.js.br +0 -0
- package/dist/_frontend/assets/tsMode-Ctn4PuNt.js.gz +0 -0
- package/dist/_frontend/assets/{typescript-BfXupYGq.js → typescript-DGwzICBa.js} +1 -1
- package/dist/_frontend/assets/typescript-DGwzICBa.js.br +0 -0
- package/dist/_frontend/assets/typescript-DGwzICBa.js.gz +0 -0
- package/dist/_frontend/assets/{workers-DCX6qMOc.js → workers-CRRTWuKZ.js} +1 -1
- package/dist/_frontend/assets/workers-CRRTWuKZ.js.br +0 -0
- package/dist/_frontend/assets/workers-CRRTWuKZ.js.gz +0 -0
- package/dist/_frontend/assets/{xml-MX-qfsGl.js → xml-CFZg-QqB.js} +1 -1
- package/dist/_frontend/assets/xml-CFZg-QqB.js.br +0 -0
- package/dist/_frontend/assets/xml-CFZg-QqB.js.gz +0 -0
- package/dist/_frontend/assets/{yaml-DnKyyO9e.js → yaml-DUIIx9CV.js} +1 -1
- package/dist/_frontend/assets/yaml-DUIIx9CV.js.br +0 -0
- package/dist/_frontend/assets/yaml-DUIIx9CV.js.gz +0 -0
- package/dist/_frontend/index.html +1 -1
- package/dist/_frontend/index.html.br +0 -0
- package/dist/_frontend/index.html.gz +0 -0
- package/dist/main.js +2198 -519
- package/dist/main.js.map +1 -1
- package/dist/postgres/migrations/20260818_000001_mcp_pending_auth_return_to.js +17 -0
- package/dist/postgres/migrations/20260818_000001_mcp_pending_auth_return_to.js.map +1 -0
- package/dist/sqlite/migrations/20260818_000001_mcp_pending_auth_return_to.js +19 -0
- package/dist/sqlite/migrations/20260818_000001_mcp_pending_auth_return_to.js.map +1 -0
- package/package.json +10 -4
- package/dist/_frontend/assets/ConnectorSettings-XEO5ZQO5-BXis0tQK.js.br +0 -0
- package/dist/_frontend/assets/ConnectorSettings-XEO5ZQO5-BXis0tQK.js.gz +0 -0
- package/dist/_frontend/assets/DrawerLayout-FJ7XC2FU-ChH-Qykr.js.br +0 -0
- package/dist/_frontend/assets/DrawerLayout-FJ7XC2FU-ChH-Qykr.js.gz +0 -0
- package/dist/_frontend/assets/ModelSettings-2QB7LE22-CDZku4g3.js.br +0 -0
- package/dist/_frontend/assets/ModelSettings-2QB7LE22-CDZku4g3.js.gz +0 -0
- package/dist/_frontend/assets/SandboxSettings-QDDIPRBP-CImJLa0r.js.br +0 -0
- package/dist/_frontend/assets/SandboxSettings-QDDIPRBP-CImJLa0r.js.gz +0 -0
- package/dist/_frontend/assets/SettingsBuilder-4NF3Y7UZ-eXXDLQ6k.js +0 -2
- package/dist/_frontend/assets/SettingsBuilder-4NF3Y7UZ-eXXDLQ6k.js.br +0 -0
- package/dist/_frontend/assets/SettingsBuilder-4NF3Y7UZ-eXXDLQ6k.js.gz +0 -0
- package/dist/_frontend/assets/ShellRouteSync-T4WLY57Q-CetHx5EA.js.br +0 -0
- package/dist/_frontend/assets/ShellRouteSync-T4WLY57Q-CetHx5EA.js.gz +0 -0
- package/dist/_frontend/assets/SidebarLayout-KX6RSJTP-B8G4CTTb.js.br +0 -0
- package/dist/_frontend/assets/SidebarLayout-KX6RSJTP-B8G4CTTb.js.gz +0 -0
- package/dist/_frontend/assets/SkillSettings-5EETEEDI-DbJE0Ms2.js.br +0 -0
- package/dist/_frontend/assets/SkillSettings-5EETEEDI-DbJE0Ms2.js.gz +0 -0
- package/dist/_frontend/assets/WidgetLayout-S6EK62TZ-BPf7T3wa.js.br +0 -0
- package/dist/_frontend/assets/WidgetLayout-S6EK62TZ-BPf7T3wa.js.gz +0 -0
- package/dist/_frontend/assets/chunk-62JRHF6Z-BUuLA42_.js.br +0 -0
- package/dist/_frontend/assets/chunk-62JRHF6Z-BUuLA42_.js.gz +0 -0
- package/dist/_frontend/assets/chunk-PTYEOSHT-C4gdhLwo.js.br +0 -0
- package/dist/_frontend/assets/chunk-PTYEOSHT-C4gdhLwo.js.gz +0 -0
- package/dist/_frontend/assets/chunk-X6U2Q7X5-DDlfUle1.js.br +0 -0
- package/dist/_frontend/assets/chunk-X6U2Q7X5-DDlfUle1.js.gz +0 -0
- package/dist/_frontend/assets/cssMode-C1whwlDW.js.br +0 -0
- package/dist/_frontend/assets/cssMode-C1whwlDW.js.gz +0 -0
- package/dist/_frontend/assets/freemarker2-CaMIS9bY.js.br +0 -0
- package/dist/_frontend/assets/freemarker2-CaMIS9bY.js.gz +0 -0
- package/dist/_frontend/assets/handlebars-Donx2d9H.js.br +0 -0
- package/dist/_frontend/assets/handlebars-Donx2d9H.js.gz +0 -0
- package/dist/_frontend/assets/html-CtI8kZ6e.js.br +0 -0
- package/dist/_frontend/assets/html-CtI8kZ6e.js.gz +0 -0
- package/dist/_frontend/assets/htmlMode-B6ky8VkF.js.br +0 -0
- package/dist/_frontend/assets/htmlMode-B6ky8VkF.js.gz +0 -0
- package/dist/_frontend/assets/index-DFmgzNuj.js.br +0 -0
- package/dist/_frontend/assets/index-DFmgzNuj.js.gz +0 -0
- package/dist/_frontend/assets/index-DWr7ad72.js.br +0 -0
- package/dist/_frontend/assets/index-DWr7ad72.js.gz +0 -0
- package/dist/_frontend/assets/jsonMode-B7OogXWW.js.br +0 -0
- package/dist/_frontend/assets/jsonMode-B7OogXWW.js.gz +0 -0
- package/dist/_frontend/assets/liquid-D_8RkuUW.js.br +0 -0
- package/dist/_frontend/assets/liquid-D_8RkuUW.js.gz +0 -0
- package/dist/_frontend/assets/lspLanguageFeatures-DJf8_W2N.js.br +0 -0
- package/dist/_frontend/assets/lspLanguageFeatures-DJf8_W2N.js.gz +0 -0
- package/dist/_frontend/assets/mdx-HcpZSZ2w.js.br +0 -0
- package/dist/_frontend/assets/mdx-HcpZSZ2w.js.gz +0 -0
- package/dist/_frontend/assets/python-CPiT1vB4.js.br +0 -0
- package/dist/_frontend/assets/python-CPiT1vB4.js.gz +0 -0
- package/dist/_frontend/assets/razor-oizHS-vq.js.br +0 -0
- package/dist/_frontend/assets/razor-oizHS-vq.js.gz +0 -0
- package/dist/_frontend/assets/tsMode-n5kkpKJX.js.br +0 -0
- package/dist/_frontend/assets/tsMode-n5kkpKJX.js.gz +0 -0
- package/dist/_frontend/assets/typescript-BfXupYGq.js.br +0 -0
- package/dist/_frontend/assets/typescript-BfXupYGq.js.gz +0 -0
- package/dist/_frontend/assets/workers-DCX6qMOc.js.br +0 -0
- package/dist/_frontend/assets/workers-DCX6qMOc.js.gz +0 -0
- package/dist/_frontend/assets/xml-MX-qfsGl.js.br +0 -0
- package/dist/_frontend/assets/xml-MX-qfsGl.js.gz +0 -0
- package/dist/_frontend/assets/yaml-DnKyyO9e.js.br +0 -0
- package/dist/_frontend/assets/yaml-DnKyyO9e.js.gz +0 -0
package/dist/main.js
CHANGED
|
@@ -8,28 +8,6 @@ var __export = (target, all) => {
|
|
|
8
8
|
__defProp(target, name, { get: all[name], enumerable: true });
|
|
9
9
|
};
|
|
10
10
|
|
|
11
|
-
// src/db/agentStore.ts
|
|
12
|
-
import { AgentSpecSchema } from "@truefoundry/trueforge-core/agent-session";
|
|
13
|
-
function parseStoredAgentSpec(manifest) {
|
|
14
|
-
return AgentSpecSchema.parse(manifest);
|
|
15
|
-
}
|
|
16
|
-
var AgentNameConflictError;
|
|
17
|
-
var init_agentStore = __esm({
|
|
18
|
-
"src/db/agentStore.ts"() {
|
|
19
|
-
"use strict";
|
|
20
|
-
AgentNameConflictError = class extends Error {
|
|
21
|
-
tenant_id;
|
|
22
|
-
agent_name;
|
|
23
|
-
constructor({ tenant_id, name }, options) {
|
|
24
|
-
super(`Agent name already exists: ${name}`, options);
|
|
25
|
-
this.name = "AgentNameConflictError";
|
|
26
|
-
this.tenant_id = tenant_id;
|
|
27
|
-
this.agent_name = name;
|
|
28
|
-
}
|
|
29
|
-
};
|
|
30
|
-
}
|
|
31
|
-
});
|
|
32
|
-
|
|
33
11
|
// src/config.ts
|
|
34
12
|
var config_exports = {};
|
|
35
13
|
__export(config_exports, {
|
|
@@ -38,7 +16,8 @@ __export(config_exports, {
|
|
|
38
16
|
isOidcConfigured: () => isOidcConfigured,
|
|
39
17
|
parseOidcScopes: () => parseOidcScopes
|
|
40
18
|
});
|
|
41
|
-
import { existsSync } from "fs";
|
|
19
|
+
import { existsSync as existsSync2 } from "fs";
|
|
20
|
+
import os from "os";
|
|
42
21
|
import path from "path";
|
|
43
22
|
import { fileURLToPath } from "url";
|
|
44
23
|
import envPaths from "env-paths";
|
|
@@ -102,7 +81,7 @@ function parseBoolean(options) {
|
|
|
102
81
|
}
|
|
103
82
|
function resolveDefaultFrontendDir() {
|
|
104
83
|
const packaged = path.join(PACKAGE_ROOT, "dist", "_frontend");
|
|
105
|
-
if (
|
|
84
|
+
if (existsSync2(path.join(packaged, "index.html"))) {
|
|
106
85
|
return packaged;
|
|
107
86
|
}
|
|
108
87
|
return path.join(PACKAGE_ROOT, "..", "frontend", "dist");
|
|
@@ -129,6 +108,12 @@ function resolveSqlitePath() {
|
|
|
129
108
|
const paths = envPaths(ENV_PATHS_APP_NAME, { suffix: "" });
|
|
130
109
|
return path.join(paths.data, "db", "db.sqlite");
|
|
131
110
|
}
|
|
111
|
+
function resolveLocalSandboxRootParent() {
|
|
112
|
+
return path.join(envPaths(ENV_PATHS_APP_NAME, { suffix: "" }).data, "sandboxes");
|
|
113
|
+
}
|
|
114
|
+
function resolveCodeModeSocketParent() {
|
|
115
|
+
return path.join(os.tmpdir(), "tf_cms");
|
|
116
|
+
}
|
|
132
117
|
function resolveRedisUrl() {
|
|
133
118
|
const raw = getEnv("REDIS_URL", { defaultValue: DEFAULT_REDIS_URL }) ?? DEFAULT_REDIS_URL;
|
|
134
119
|
if (raw.trim() === "") {
|
|
@@ -305,7 +290,9 @@ var init_config = __esm({
|
|
|
305
290
|
configuration = standalone ? {
|
|
306
291
|
...shared,
|
|
307
292
|
STANDALONE: true,
|
|
308
|
-
SQLITE_PATH: resolveSqlitePath()
|
|
293
|
+
SQLITE_PATH: resolveSqlitePath(),
|
|
294
|
+
LOCAL_SANDBOX_ROOT_PARENT: resolveLocalSandboxRootParent(),
|
|
295
|
+
CODE_MODE_SOCKET_PARENT: resolveCodeModeSocketParent()
|
|
309
296
|
} : {
|
|
310
297
|
...shared,
|
|
311
298
|
STANDALONE: false,
|
|
@@ -329,7 +316,1609 @@ var init_config = __esm({
|
|
|
329
316
|
REDIS_URL: resolveRedisUrl(),
|
|
330
317
|
OIDC: resolveOIDCConfig()
|
|
331
318
|
};
|
|
332
|
-
config_default = configuration;
|
|
319
|
+
config_default = configuration;
|
|
320
|
+
}
|
|
321
|
+
});
|
|
322
|
+
|
|
323
|
+
// src/db/agentStore.ts
|
|
324
|
+
import { AgentSpecSchema } from "@truefoundry/trueforge-core/agent-session";
|
|
325
|
+
function parseStoredAgentSpec(manifest) {
|
|
326
|
+
return AgentSpecSchema.parse(manifest);
|
|
327
|
+
}
|
|
328
|
+
var AgentNameConflictError;
|
|
329
|
+
var init_agentStore = __esm({
|
|
330
|
+
"src/db/agentStore.ts"() {
|
|
331
|
+
"use strict";
|
|
332
|
+
AgentNameConflictError = class extends Error {
|
|
333
|
+
tenant_id;
|
|
334
|
+
agent_name;
|
|
335
|
+
constructor({ tenant_id, name }, options) {
|
|
336
|
+
super(`Agent name already exists: ${name}`, options);
|
|
337
|
+
this.name = "AgentNameConflictError";
|
|
338
|
+
this.tenant_id = tenant_id;
|
|
339
|
+
this.agent_name = name;
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
// src/sandbox/local/sandboxScripts.gen.ts
|
|
346
|
+
var sandboxScripts;
|
|
347
|
+
var init_sandboxScripts_gen = __esm({
|
|
348
|
+
"src/sandbox/local/sandboxScripts.gen.ts"() {
|
|
349
|
+
"use strict";
|
|
350
|
+
sandboxScripts = {
|
|
351
|
+
mcpClientLocal: `#!/usr/bin/env python3
|
|
352
|
+
"""Code Mode UDS client \u2014 same public surface as product mcp_client.py, stdlib only.
|
|
353
|
+
|
|
354
|
+
Authoritative reference (do not diverge on API/CLI/policy semantics):
|
|
355
|
+
packages/trueforge-core/src/core/sandbox/scripts/mcp_client.py
|
|
356
|
+
|
|
357
|
+
Inlined into TypeScript via scripts/generate-local-sandbox-scripts.mjs
|
|
358
|
+
(src/sandbox/local/sandboxScripts.gen.ts), same pattern as core sandboxScripts.
|
|
359
|
+
"""
|
|
360
|
+
|
|
361
|
+
from __future__ import annotations
|
|
362
|
+
|
|
363
|
+
import argparse
|
|
364
|
+
import asyncio
|
|
365
|
+
import base64
|
|
366
|
+
import json
|
|
367
|
+
import logging
|
|
368
|
+
import os
|
|
369
|
+
import socket
|
|
370
|
+
import sys
|
|
371
|
+
import time
|
|
372
|
+
from pathlib import Path
|
|
373
|
+
from typing import Any, TypedDict
|
|
374
|
+
|
|
375
|
+
logger = logging.getLogger(__name__)
|
|
376
|
+
|
|
377
|
+
MAX_MESSAGE_BYTES = 64 * 1024 * 1024
|
|
378
|
+
_TOOLS_CACHE_TTL_SECONDS = 600
|
|
379
|
+
|
|
380
|
+
|
|
381
|
+
class _ServerConfig(TypedDict):
|
|
382
|
+
allowed_tools: list[str]
|
|
383
|
+
|
|
384
|
+
|
|
385
|
+
_inflight_list_tools: dict[str, asyncio.Task[list[dict[str, Any]]]] = {}
|
|
386
|
+
|
|
387
|
+
_raw_servers = os.environ.get("TFY_MCP_SERVERS")
|
|
388
|
+
_servers_map: dict[str, _ServerConfig] = (
|
|
389
|
+
json.loads(base64.b64decode(_raw_servers).decode()) if _raw_servers else {}
|
|
390
|
+
)
|
|
391
|
+
_enable_agent_approvals = os.environ.get("TFY_ENABLE_AGENT_APPROVALS", "true").lower() == "true"
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def _sock_path() -> str:
|
|
395
|
+
path = os.environ.get("TFY_MCP_SOCK")
|
|
396
|
+
if not path:
|
|
397
|
+
raise RuntimeError("TFY_MCP_SOCK is not set")
|
|
398
|
+
return path
|
|
399
|
+
|
|
400
|
+
|
|
401
|
+
def _request_timeout() -> float:
|
|
402
|
+
return float(os.environ.get("TFY_CM_REQUEST_TIMEOUT_SECONDS", "60"))
|
|
403
|
+
|
|
404
|
+
|
|
405
|
+
def _check_tool_allowed(server: str, tool_name: str) -> None:
|
|
406
|
+
server_config = _servers_map.get(server)
|
|
407
|
+
if server_config is None:
|
|
408
|
+
raise RuntimeError(f"Access denied: MCP server '{server}' is not available for this agent") from None
|
|
409
|
+
server_tools = server_config.get("allowed_tools") or []
|
|
410
|
+
if len(server_tools) > 0 and tool_name not in server_tools:
|
|
411
|
+
raise RuntimeError(f"Access denied: tool '{tool_name}' is not enabled on server '{server}'") from None
|
|
412
|
+
|
|
413
|
+
|
|
414
|
+
def _cache_path(server: str) -> Path:
|
|
415
|
+
return Path(__file__).parent / f"{server}.tools.json"
|
|
416
|
+
|
|
417
|
+
|
|
418
|
+
def _read_tools_cache(server: str) -> list[dict[str, Any]] | None:
|
|
419
|
+
p = _cache_path(server)
|
|
420
|
+
if not p.exists():
|
|
421
|
+
return None
|
|
422
|
+
try:
|
|
423
|
+
raw = json.loads(p.read_text(encoding="utf-8"))
|
|
424
|
+
if not isinstance(raw, dict):
|
|
425
|
+
raise ValueError("cache root must be object")
|
|
426
|
+
fetched_at = raw.get("fetched_at")
|
|
427
|
+
tools = raw.get("tools")
|
|
428
|
+
if not isinstance(fetched_at, (int, float)) or not isinstance(tools, list):
|
|
429
|
+
raise ValueError("cache shape invalid")
|
|
430
|
+
if time.time() - float(fetched_at) > _TOOLS_CACHE_TTL_SECONDS:
|
|
431
|
+
p.unlink(missing_ok=True)
|
|
432
|
+
return None
|
|
433
|
+
return [t for t in tools if isinstance(t, dict)]
|
|
434
|
+
except Exception:
|
|
435
|
+
logger.exception("_read_tools_cache")
|
|
436
|
+
p.unlink(missing_ok=True)
|
|
437
|
+
return None
|
|
438
|
+
|
|
439
|
+
|
|
440
|
+
def _write_tools_cache(server: str, tools: list[dict[str, Any]]) -> None:
|
|
441
|
+
try:
|
|
442
|
+
payload = {"fetched_at": time.time(), "tools": tools}
|
|
443
|
+
_cache_path(server).write_text(json.dumps(payload), encoding="utf-8")
|
|
444
|
+
except Exception:
|
|
445
|
+
logger.exception("_write_tools_cache")
|
|
446
|
+
|
|
447
|
+
|
|
448
|
+
def _read_message(sock: socket.socket) -> Any:
|
|
449
|
+
body = b""
|
|
450
|
+
while True:
|
|
451
|
+
chunk = sock.recv(65536)
|
|
452
|
+
if not chunk:
|
|
453
|
+
break
|
|
454
|
+
body += chunk
|
|
455
|
+
if len(body) > MAX_MESSAGE_BYTES:
|
|
456
|
+
raise RuntimeError(f"message exceeds max {MAX_MESSAGE_BYTES} bytes")
|
|
457
|
+
return json.loads(body.decode("utf-8"))
|
|
458
|
+
|
|
459
|
+
|
|
460
|
+
def _write_message(sock: socket.socket, value: Any) -> None:
|
|
461
|
+
sock.sendall(json.dumps(value).encode("utf-8"))
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _uds_request_sync(payload: dict[str, Any]) -> Any:
|
|
465
|
+
"""Connect \u2192 JSON request \u2192 write-close \u2192 JSON reply (no request_id)."""
|
|
466
|
+
sock = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM)
|
|
467
|
+
try:
|
|
468
|
+
sock.settimeout(_request_timeout())
|
|
469
|
+
sock.connect(_sock_path())
|
|
470
|
+
_write_message(sock, payload)
|
|
471
|
+
sock.shutdown(socket.SHUT_WR)
|
|
472
|
+
reply = _read_message(sock)
|
|
473
|
+
finally:
|
|
474
|
+
sock.close()
|
|
475
|
+
if not isinstance(reply, dict):
|
|
476
|
+
raise RuntimeError(f"Code Mode reply is not an object: {reply!r}")
|
|
477
|
+
ok = reply.get("ok")
|
|
478
|
+
if ok is not True:
|
|
479
|
+
source = reply.get("source", "internal")
|
|
480
|
+
error = reply.get("error", "unknown error")
|
|
481
|
+
if source == "caller":
|
|
482
|
+
raise RuntimeError(f"Invalid MCP request: {error}")
|
|
483
|
+
if source == "transport":
|
|
484
|
+
raise RuntimeError(f"Code Mode transport error: {error}")
|
|
485
|
+
raise RuntimeError(f"Internal MCP error: {error}")
|
|
486
|
+
return reply.get("result")
|
|
487
|
+
|
|
488
|
+
|
|
489
|
+
async def _uds_request(payload: dict[str, Any]) -> Any:
|
|
490
|
+
return await asyncio.to_thread(_uds_request_sync, payload)
|
|
491
|
+
|
|
492
|
+
|
|
493
|
+
async def _fetch_tools(server: str) -> list[dict[str, Any]]:
|
|
494
|
+
result = await _uds_request({"op": "list_tools", "server": server})
|
|
495
|
+
if not isinstance(result, dict):
|
|
496
|
+
raise RuntimeError(f"list_tools '{server}' returned unexpected shape: {result!r}") from None
|
|
497
|
+
tools = result.get("tools", [])
|
|
498
|
+
if not isinstance(tools, list):
|
|
499
|
+
raise RuntimeError(f"list_tools '{server}' tools is not a list: {tools!r}") from None
|
|
500
|
+
return [t for t in tools if isinstance(t, dict)]
|
|
501
|
+
|
|
502
|
+
|
|
503
|
+
async def _fetch_and_cache_tools(server: str) -> list[dict[str, Any]]:
|
|
504
|
+
tools = await _fetch_tools(server)
|
|
505
|
+
_write_tools_cache(server, tools)
|
|
506
|
+
return tools
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
async def _get_tools(server: str) -> list[dict[str, Any]]:
|
|
510
|
+
cached = _read_tools_cache(server)
|
|
511
|
+
if cached is not None:
|
|
512
|
+
return cached
|
|
513
|
+
task = _inflight_list_tools.get(server)
|
|
514
|
+
if task is None:
|
|
515
|
+
task = asyncio.create_task(_fetch_and_cache_tools(server))
|
|
516
|
+
_inflight_list_tools[server] = task
|
|
517
|
+
task.add_done_callback(lambda _t: _inflight_list_tools.pop(server, None))
|
|
518
|
+
return await task
|
|
519
|
+
|
|
520
|
+
|
|
521
|
+
async def _get_tool(server: str, tool_name: str) -> dict[str, Any] | None:
|
|
522
|
+
for t in await _get_tools(server):
|
|
523
|
+
if t.get("name") == tool_name:
|
|
524
|
+
return t
|
|
525
|
+
return None
|
|
526
|
+
|
|
527
|
+
|
|
528
|
+
def _is_destructive(tool: dict[str, Any]) -> bool:
|
|
529
|
+
annotations = tool.get("annotations")
|
|
530
|
+
if annotations is None:
|
|
531
|
+
return False
|
|
532
|
+
if not isinstance(annotations, dict):
|
|
533
|
+
return False
|
|
534
|
+
destructive = annotations.get("destructiveHint")
|
|
535
|
+
read_only = annotations.get("readOnlyHint")
|
|
536
|
+
return bool(destructive) or (not read_only and read_only is not None)
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
async def _ensure_non_destructive(server: str, tool_name: str) -> None:
|
|
540
|
+
tool = await _get_tool(server, tool_name)
|
|
541
|
+
if tool is None:
|
|
542
|
+
raise RuntimeError(f"Tool '{tool_name}' not found on MCP server '{server}'") from None
|
|
543
|
+
if _is_destructive(tool):
|
|
544
|
+
raise RuntimeError(
|
|
545
|
+
f"Tool '{tool_name}' on MCP server '{server}' is destructive and cannot be called in Code Mode; "
|
|
546
|
+
f"call it directly so it can go through the user approval flow"
|
|
547
|
+
) from None
|
|
548
|
+
|
|
549
|
+
|
|
550
|
+
def _project_call_tool_result(server: str, tool: str, result: Any) -> Any:
|
|
551
|
+
"""Project an MCP-wire CallToolResult-shaped object into the user-facing Python value."""
|
|
552
|
+
if not isinstance(result, dict):
|
|
553
|
+
raise RuntimeError(f"call_tool reply for '{server}/{tool}' is malformed: expected object") from None
|
|
554
|
+
|
|
555
|
+
if result.get("isError"):
|
|
556
|
+
content = result.get("content")
|
|
557
|
+
text_parts: list[str] = []
|
|
558
|
+
if isinstance(content, list):
|
|
559
|
+
for c in content:
|
|
560
|
+
if isinstance(c, dict) and c.get("type") == "text":
|
|
561
|
+
text = c.get("text")
|
|
562
|
+
if isinstance(text, str) and text:
|
|
563
|
+
text_parts.append(text)
|
|
564
|
+
msg = "; ".join(text_parts) if text_parts else "tool returned an error"
|
|
565
|
+
raise RuntimeError(f"MCP tool error (server={server}, tool={tool}): {msg}") from None
|
|
566
|
+
|
|
567
|
+
if result.get("structuredContent") is not None:
|
|
568
|
+
return result["structuredContent"]
|
|
569
|
+
|
|
570
|
+
content = result.get("content")
|
|
571
|
+
if isinstance(content, list) and len(content) == 1:
|
|
572
|
+
first = content[0]
|
|
573
|
+
if isinstance(first, dict) and first.get("type") == "text":
|
|
574
|
+
text = first.get("text")
|
|
575
|
+
if isinstance(text, str) and text:
|
|
576
|
+
try:
|
|
577
|
+
return json.loads(text)
|
|
578
|
+
except Exception:
|
|
579
|
+
pass
|
|
580
|
+
if isinstance(content, list) and content:
|
|
581
|
+
return content
|
|
582
|
+
return None
|
|
583
|
+
|
|
584
|
+
|
|
585
|
+
async def call_tool(server: str, tool: str, body: dict[str, Any]) -> Any:
|
|
586
|
+
_check_tool_allowed(server, tool)
|
|
587
|
+
if _enable_agent_approvals:
|
|
588
|
+
await _ensure_non_destructive(server, tool)
|
|
589
|
+
|
|
590
|
+
raw = await _uds_request(
|
|
591
|
+
{
|
|
592
|
+
"op": "call_tool",
|
|
593
|
+
"server": server,
|
|
594
|
+
"tool": tool,
|
|
595
|
+
"arguments": body,
|
|
596
|
+
},
|
|
597
|
+
)
|
|
598
|
+
return _project_call_tool_result(server, tool, raw)
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
_USAGE = "mcp_client_local.py call-tool <server> <tool> <args-json>"
|
|
602
|
+
|
|
603
|
+
|
|
604
|
+
def _build_arg_parser() -> argparse.ArgumentParser:
|
|
605
|
+
parser = argparse.ArgumentParser(prog="mcp_client_local.py", usage=_USAGE)
|
|
606
|
+
sub = parser.add_subparsers(dest="cmd", required=True)
|
|
607
|
+
|
|
608
|
+
call_tool_p = sub.add_parser("call-tool", help="Invoke an MCP tool")
|
|
609
|
+
call_tool_p.add_argument("server")
|
|
610
|
+
call_tool_p.add_argument("tool")
|
|
611
|
+
call_tool_p.add_argument("args_json", metavar="args-json", type=json.loads)
|
|
612
|
+
|
|
613
|
+
return parser
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
async def _main() -> None:
|
|
617
|
+
args = _build_arg_parser().parse_args()
|
|
618
|
+
try:
|
|
619
|
+
if args.cmd == "call-tool":
|
|
620
|
+
result = await call_tool(args.server, args.tool, args.args_json)
|
|
621
|
+
print(json.dumps(result, default=str))
|
|
622
|
+
except RuntimeError as e:
|
|
623
|
+
sys.exit(str(e))
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
if __name__ == "__main__":
|
|
627
|
+
asyncio.run(_main())
|
|
628
|
+
`
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
});
|
|
632
|
+
|
|
633
|
+
// src/sandbox/local/schemas/jsonMessage.ts
|
|
634
|
+
import { z as z5 } from "zod";
|
|
635
|
+
var JsonMessageValueSchema;
|
|
636
|
+
var init_jsonMessage = __esm({
|
|
637
|
+
"src/sandbox/local/schemas/jsonMessage.ts"() {
|
|
638
|
+
"use strict";
|
|
639
|
+
JsonMessageValueSchema = z5.json();
|
|
640
|
+
}
|
|
641
|
+
});
|
|
642
|
+
|
|
643
|
+
// src/sandbox/local/core/frame.ts
|
|
644
|
+
function encodeJsonMessage(value) {
|
|
645
|
+
return Buffer.from(JSON.stringify(value), "utf8");
|
|
646
|
+
}
|
|
647
|
+
var MAX_MESSAGE_BYTES, JsonMessageReader;
|
|
648
|
+
var init_frame = __esm({
|
|
649
|
+
"src/sandbox/local/core/frame.ts"() {
|
|
650
|
+
"use strict";
|
|
651
|
+
init_jsonMessage();
|
|
652
|
+
MAX_MESSAGE_BYTES = 64 * 1024 * 1024;
|
|
653
|
+
JsonMessageReader = class {
|
|
654
|
+
#buffer = Buffer.alloc(0);
|
|
655
|
+
#maxBytes;
|
|
656
|
+
constructor(options = {}) {
|
|
657
|
+
this.#maxBytes = options.maxBytes ?? MAX_MESSAGE_BYTES;
|
|
658
|
+
}
|
|
659
|
+
push(chunk) {
|
|
660
|
+
if (this.#buffer.length + chunk.length > this.#maxBytes) {
|
|
661
|
+
throw new Error(`message exceeds max ${String(this.#maxBytes)} bytes`);
|
|
662
|
+
}
|
|
663
|
+
this.#buffer = Buffer.concat([this.#buffer, chunk]);
|
|
664
|
+
}
|
|
665
|
+
finish() {
|
|
666
|
+
try {
|
|
667
|
+
return JsonMessageValueSchema.parse(JSON.parse(this.#buffer.toString("utf8")));
|
|
668
|
+
} catch (error) {
|
|
669
|
+
throw new Error("invalid JSON message", { cause: error });
|
|
670
|
+
}
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
}
|
|
674
|
+
});
|
|
675
|
+
|
|
676
|
+
// src/sandbox/local/core/CodeModeUdsTransport.ts
|
|
677
|
+
import { CodeModeRequestSchema, validateNoPathTraversal } from "@truefoundry/trueforge-core/core";
|
|
678
|
+
import { chmodSync, existsSync as existsSync3, realpathSync, statSync } from "fs";
|
|
679
|
+
import { chmod, mkdir as mkdir2, rm as rm2, symlink, unlink, writeFile } from "fs/promises";
|
|
680
|
+
import { createServer } from "net";
|
|
681
|
+
import { dirname, isAbsolute, join, resolve } from "path";
|
|
682
|
+
import { ulid } from "ulid";
|
|
683
|
+
function assertCodeModeSocketParentPath(path7) {
|
|
684
|
+
if (!isAbsolute(path7)) {
|
|
685
|
+
throw new Error("codeModeSocketParentPath must be an absolute path");
|
|
686
|
+
}
|
|
687
|
+
validateNoPathTraversal(path7);
|
|
688
|
+
const resolved = resolve(path7);
|
|
689
|
+
if (!existsSync3(resolved) || !statSync(resolved).isDirectory()) {
|
|
690
|
+
throw new Error("codeModeSocketParentPath must be an existing directory");
|
|
691
|
+
}
|
|
692
|
+
const real = realpathSync(resolved);
|
|
693
|
+
const bytes = Buffer.byteLength(real);
|
|
694
|
+
if (bytes > MAX_CODE_MODE_SOCKET_PARENT_BYTES) {
|
|
695
|
+
throw new Error(
|
|
696
|
+
`codeModeSocketParentPath (${real}) must be at most ${String(MAX_CODE_MODE_SOCKET_PARENT_BYTES)} bytes (got ${String(bytes)})`
|
|
697
|
+
);
|
|
698
|
+
}
|
|
699
|
+
chmodSync(real, CODE_MODE_SOCKET_PARENT_MODE);
|
|
700
|
+
const mode = statSync(real).mode & 511;
|
|
701
|
+
if (mode !== CODE_MODE_SOCKET_PARENT_MODE) {
|
|
702
|
+
throw new Error(`codeModeSocketParentPath must be mode 0700 after chmod (got 0o${mode.toString(8)})`);
|
|
703
|
+
}
|
|
704
|
+
return real;
|
|
705
|
+
}
|
|
706
|
+
async function probeCodeModeUnixSocket(parentPath) {
|
|
707
|
+
const realParent = assertCodeModeSocketParentPath(parentPath);
|
|
708
|
+
const sockPath = join(realParent, ulid().toLowerCase());
|
|
709
|
+
await unlink(sockPath).catch(() => void 0);
|
|
710
|
+
const server = createServer();
|
|
711
|
+
try {
|
|
712
|
+
await new Promise((resolveListen, reject) => {
|
|
713
|
+
server.once("error", reject);
|
|
714
|
+
server.listen(sockPath, () => {
|
|
715
|
+
server.off("error", reject);
|
|
716
|
+
resolveListen();
|
|
717
|
+
});
|
|
718
|
+
});
|
|
719
|
+
} catch (error) {
|
|
720
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
721
|
+
throw new Error(`listen ${sockPath} (${String(Buffer.byteLength(sockPath))} bytes): ${message}`, { cause: error });
|
|
722
|
+
} finally {
|
|
723
|
+
await new Promise((resolveClose) => {
|
|
724
|
+
server.close(() => {
|
|
725
|
+
resolveClose();
|
|
726
|
+
});
|
|
727
|
+
});
|
|
728
|
+
await unlink(sockPath).catch(() => void 0);
|
|
729
|
+
}
|
|
730
|
+
return { sockPath };
|
|
731
|
+
}
|
|
732
|
+
var MAX_CODE_MODE_SOCKET_PARENT_BYTES, CODE_MODE_SOCKET_PARENT_MODE, CODE_MODE_SOCKET_MODE, CodeModeUdsTransport;
|
|
733
|
+
var init_CodeModeUdsTransport = __esm({
|
|
734
|
+
"src/sandbox/local/core/CodeModeUdsTransport.ts"() {
|
|
735
|
+
"use strict";
|
|
736
|
+
init_sandboxScripts_gen();
|
|
737
|
+
init_frame();
|
|
738
|
+
MAX_CODE_MODE_SOCKET_PARENT_BYTES = 65;
|
|
739
|
+
CODE_MODE_SOCKET_PARENT_MODE = 448;
|
|
740
|
+
CODE_MODE_SOCKET_MODE = 384;
|
|
741
|
+
CodeModeUdsTransport = class {
|
|
742
|
+
codeModeSocketParentPath;
|
|
743
|
+
maxMessageBytes;
|
|
744
|
+
onProtocolError;
|
|
745
|
+
clientRemotePath;
|
|
746
|
+
sessionPromise;
|
|
747
|
+
server;
|
|
748
|
+
sockPath;
|
|
749
|
+
dispatcher;
|
|
750
|
+
cachedEnv;
|
|
751
|
+
constructor(options) {
|
|
752
|
+
this.codeModeSocketParentPath = assertCodeModeSocketParentPath(options.codeModeSocketParentPath);
|
|
753
|
+
this.maxMessageBytes = options.maxMessageBytes ?? MAX_MESSAGE_BYTES;
|
|
754
|
+
this.onProtocolError = options.onProtocolError;
|
|
755
|
+
this.clientRemotePath = options.clientRemotePath;
|
|
756
|
+
}
|
|
757
|
+
getClientInstall(params) {
|
|
758
|
+
return {
|
|
759
|
+
content: sandboxScripts.mcpClientLocal,
|
|
760
|
+
remotePath: this.clientRemotePath(params.sandboxId)
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
start(params) {
|
|
764
|
+
this.dispatcher = params.codeModeDispatcher;
|
|
765
|
+
this.sessionPromise ??= this.listenSession(params).catch((e) => {
|
|
766
|
+
this.sessionPromise = void 0;
|
|
767
|
+
this.cachedEnv = void 0;
|
|
768
|
+
throw e;
|
|
769
|
+
});
|
|
770
|
+
return this.sessionPromise;
|
|
771
|
+
}
|
|
772
|
+
async stop() {
|
|
773
|
+
const pending = this.sessionPromise;
|
|
774
|
+
this.sessionPromise = void 0;
|
|
775
|
+
this.cachedEnv = void 0;
|
|
776
|
+
if (pending !== void 0) {
|
|
777
|
+
try {
|
|
778
|
+
await pending;
|
|
779
|
+
} catch {
|
|
780
|
+
}
|
|
781
|
+
}
|
|
782
|
+
const server = this.server;
|
|
783
|
+
const sockPath = this.sockPath;
|
|
784
|
+
this.server = void 0;
|
|
785
|
+
this.sockPath = void 0;
|
|
786
|
+
if (server !== void 0) {
|
|
787
|
+
await new Promise((resolveClose) => {
|
|
788
|
+
server.close(() => {
|
|
789
|
+
resolveClose();
|
|
790
|
+
});
|
|
791
|
+
});
|
|
792
|
+
}
|
|
793
|
+
if (sockPath !== void 0) {
|
|
794
|
+
await unlink(sockPath).catch(() => void 0);
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
async listenSession(params) {
|
|
798
|
+
if (this.server !== void 0 && this.cachedEnv !== void 0) {
|
|
799
|
+
return { env: this.cachedEnv };
|
|
800
|
+
}
|
|
801
|
+
assertCodeModeSocketParentPath(this.codeModeSocketParentPath);
|
|
802
|
+
const sockPath = join(this.codeModeSocketParentPath, ulid().toLowerCase());
|
|
803
|
+
await unlink(sockPath).catch(() => void 0);
|
|
804
|
+
const server = createServer({ allowHalfOpen: true });
|
|
805
|
+
try {
|
|
806
|
+
await new Promise((resolveListen, reject) => {
|
|
807
|
+
server.once("error", reject);
|
|
808
|
+
server.listen(sockPath, () => {
|
|
809
|
+
server.off("error", reject);
|
|
810
|
+
resolveListen();
|
|
811
|
+
});
|
|
812
|
+
});
|
|
813
|
+
await chmod(sockPath, CODE_MODE_SOCKET_MODE);
|
|
814
|
+
} catch (error) {
|
|
815
|
+
server.close();
|
|
816
|
+
await unlink(sockPath).catch(() => void 0);
|
|
817
|
+
throw error;
|
|
818
|
+
}
|
|
819
|
+
this.server = server;
|
|
820
|
+
this.sockPath = sockPath;
|
|
821
|
+
server.on("connection", (socket) => {
|
|
822
|
+
this.handleConnection(socket);
|
|
823
|
+
});
|
|
824
|
+
const env = {
|
|
825
|
+
TFY_MCP_SOCK: sockPath,
|
|
826
|
+
TFY_CM_REQUEST_TIMEOUT_SECONDS: String(params.requestTimeoutSeconds)
|
|
827
|
+
};
|
|
828
|
+
this.cachedEnv = env;
|
|
829
|
+
return { env };
|
|
830
|
+
}
|
|
831
|
+
handleConnection(socket) {
|
|
832
|
+
const reader = new JsonMessageReader({ maxBytes: this.maxMessageBytes });
|
|
833
|
+
let settled = false;
|
|
834
|
+
socket.on("error", () => void 0);
|
|
835
|
+
const fail = (message) => {
|
|
836
|
+
if (settled) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
settled = true;
|
|
840
|
+
this.onProtocolError?.(message);
|
|
841
|
+
socket.destroy();
|
|
842
|
+
};
|
|
843
|
+
socket.on("data", (chunk) => {
|
|
844
|
+
try {
|
|
845
|
+
reader.push(chunk);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
fail(error instanceof Error ? error.message : String(error));
|
|
848
|
+
}
|
|
849
|
+
});
|
|
850
|
+
socket.on("end", () => {
|
|
851
|
+
if (settled) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
settled = true;
|
|
855
|
+
void this.dispatchConnection(socket, reader).catch((error) => {
|
|
856
|
+
this.onProtocolError?.(error instanceof Error ? error.message : String(error));
|
|
857
|
+
socket.destroy();
|
|
858
|
+
});
|
|
859
|
+
});
|
|
860
|
+
}
|
|
861
|
+
async dispatchConnection(socket, reader) {
|
|
862
|
+
let request;
|
|
863
|
+
try {
|
|
864
|
+
const parsed = CodeModeRequestSchema.safeParse(reader.finish());
|
|
865
|
+
if (!parsed.success) {
|
|
866
|
+
const reply2 = {
|
|
867
|
+
ok: false,
|
|
868
|
+
error: "Malformed Code Mode request",
|
|
869
|
+
source: "caller"
|
|
870
|
+
};
|
|
871
|
+
socket.write(encodeJsonMessage(reply2));
|
|
872
|
+
socket.end();
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
request = parsed.data;
|
|
876
|
+
} catch (error) {
|
|
877
|
+
this.onProtocolError?.(error instanceof Error ? error.message : String(error));
|
|
878
|
+
socket.destroy();
|
|
879
|
+
return;
|
|
880
|
+
}
|
|
881
|
+
const dispatcher = this.dispatcher;
|
|
882
|
+
if (dispatcher === void 0) {
|
|
883
|
+
const reply2 = {
|
|
884
|
+
ok: false,
|
|
885
|
+
error: "Code Mode dispatcher is not configured",
|
|
886
|
+
source: "internal"
|
|
887
|
+
};
|
|
888
|
+
socket.write(encodeJsonMessage(reply2));
|
|
889
|
+
socket.end();
|
|
890
|
+
return;
|
|
891
|
+
}
|
|
892
|
+
const reply = await dispatcher.dispatch({ request, traceCarrier: {} });
|
|
893
|
+
try {
|
|
894
|
+
socket.write(encodeJsonMessage(reply));
|
|
895
|
+
} finally {
|
|
896
|
+
socket.end();
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
}
|
|
901
|
+
});
|
|
902
|
+
|
|
903
|
+
// src/sandbox/local/core/hostRun.ts
|
|
904
|
+
import { getDefaultWritePaths, SandboxManager } from "@anthropic-ai/sandbox-runtime";
|
|
905
|
+
import { execFile, spawn } from "child_process";
|
|
906
|
+
import { randomUUID } from "crypto";
|
|
907
|
+
import { realpathSync as realpathSync2 } from "fs";
|
|
908
|
+
import { mkdir as mkdir3, rm as rm3 } from "fs/promises";
|
|
909
|
+
import { createRequire } from "module";
|
|
910
|
+
import { dirname as dirname2, isAbsolute as isAbsolute2, join as join2 } from "path";
|
|
911
|
+
import { promisify } from "util";
|
|
912
|
+
function codeModeSocketParentAllow() {
|
|
913
|
+
return codeModeSocketParentPath === void 0 ? [] : [codeModeSocketParentPath];
|
|
914
|
+
}
|
|
915
|
+
function requireActivePlatform() {
|
|
916
|
+
if (activePlatform === void 0) {
|
|
917
|
+
throw new Error("SRT platform is not set; call initSrt({ platform }) first");
|
|
918
|
+
}
|
|
919
|
+
return activePlatform;
|
|
920
|
+
}
|
|
921
|
+
function commandPath(platform) {
|
|
922
|
+
return COMMAND_PATH_BY_PLATFORM[platform];
|
|
923
|
+
}
|
|
924
|
+
function sandboxVenvPath(sandboxRootPath) {
|
|
925
|
+
return join2(sandboxRootPath, SANDBOX_VENV_DIR);
|
|
926
|
+
}
|
|
927
|
+
function allowedNetworkDomains() {
|
|
928
|
+
return [...LOCAL_SANDBOX_ALLOWED_DOMAINS];
|
|
929
|
+
}
|
|
930
|
+
function darwinSandboxNetworkShellPreamble() {
|
|
931
|
+
return [
|
|
932
|
+
'if [ -n "${HTTP_PROXY:-}" ]; then export HTTP_PROXY="${HTTP_PROXY//localhost/127.0.0.1}"; fi',
|
|
933
|
+
'if [ -n "${HTTPS_PROXY:-}" ]; then export HTTPS_PROXY="${HTTPS_PROXY//localhost/127.0.0.1}"; fi',
|
|
934
|
+
'if [ -n "${http_proxy:-}" ]; then export http_proxy="${http_proxy//localhost/127.0.0.1}"; fi',
|
|
935
|
+
'if [ -n "${https_proxy:-}" ]; then export https_proxy="${https_proxy//localhost/127.0.0.1}"; fi',
|
|
936
|
+
'if [ -n "${ALL_PROXY:-}" ]; then export ALL_PROXY="${ALL_PROXY//localhost/127.0.0.1}"; fi',
|
|
937
|
+
'if [ -n "${all_proxy:-}" ]; then export all_proxy="${all_proxy//localhost/127.0.0.1}"; fi',
|
|
938
|
+
'if [ -n "${GRPC_PROXY:-}" ]; then export GRPC_PROXY="${GRPC_PROXY//localhost/127.0.0.1}"; fi',
|
|
939
|
+
'if [ -n "${grpc_proxy:-}" ]; then export grpc_proxy="${grpc_proxy//localhost/127.0.0.1}"; fi'
|
|
940
|
+
].join("; ");
|
|
941
|
+
}
|
|
942
|
+
function wrapSandboxCommand(params) {
|
|
943
|
+
if (params.platform !== "darwin") {
|
|
944
|
+
return params.command;
|
|
945
|
+
}
|
|
946
|
+
return `${darwinSandboxNetworkShellPreamble()}; ${params.command}`;
|
|
947
|
+
}
|
|
948
|
+
function srtHostBinaryNames(platform) {
|
|
949
|
+
return SRT_HOST_BINARIES_BY_PLATFORM[platform];
|
|
950
|
+
}
|
|
951
|
+
async function checkSrtHostDependencies() {
|
|
952
|
+
const result = await SandboxManager.checkDependenciesAsync();
|
|
953
|
+
return { errors: [...result.errors], warnings: [...result.warnings] };
|
|
954
|
+
}
|
|
955
|
+
async function resolveCommandOnHost(params) {
|
|
956
|
+
if (!/^[A-Za-z0-9._+-]+$/.test(params.name)) {
|
|
957
|
+
throw new Error(`invalid command name for resolveCommandOnHost: ${params.name}`);
|
|
958
|
+
}
|
|
959
|
+
const pathEnv = params.pathEnv !== void 0 && params.pathEnv.length > 0 ? params.pathEnv : commandPath(params.platform);
|
|
960
|
+
try {
|
|
961
|
+
const { stdout } = await execFileAsync("/bin/sh", ["-c", `command -v -- ${params.name}`], {
|
|
962
|
+
env: { PATH: pathEnv },
|
|
963
|
+
encoding: "utf8"
|
|
964
|
+
});
|
|
965
|
+
const resolved = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
966
|
+
if (resolved === void 0 || resolved.length === 0 || !isAbsolute2(resolved)) {
|
|
967
|
+
return void 0;
|
|
968
|
+
}
|
|
969
|
+
return resolved;
|
|
970
|
+
} catch {
|
|
971
|
+
return void 0;
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
async function resolvePythonExecutableOnHost(params) {
|
|
975
|
+
if (!isAbsolute2(params.commandPath)) {
|
|
976
|
+
return void 0;
|
|
977
|
+
}
|
|
978
|
+
try {
|
|
979
|
+
const { stdout } = await execFileAsync(params.commandPath, ["-c", "import sys; print(sys.executable)"], {
|
|
980
|
+
encoding: "utf8",
|
|
981
|
+
timeout: 5e3
|
|
982
|
+
});
|
|
983
|
+
const executable = stdout.trim().split(/\r?\n/).filter(Boolean).at(-1);
|
|
984
|
+
if (executable === void 0 || executable.length === 0 || !isAbsolute2(executable)) {
|
|
985
|
+
return void 0;
|
|
986
|
+
}
|
|
987
|
+
return realpathSync2(executable);
|
|
988
|
+
} catch {
|
|
989
|
+
return void 0;
|
|
990
|
+
}
|
|
991
|
+
}
|
|
992
|
+
function denySharedDefaultWritePaths() {
|
|
993
|
+
return getDefaultWritePaths().filter((path7) => !path7.startsWith("/dev/"));
|
|
994
|
+
}
|
|
995
|
+
function platformAllowRead(platform) {
|
|
996
|
+
return [...ALLOW_READ_BY_PLATFORM[platform]];
|
|
997
|
+
}
|
|
998
|
+
function filesystemPolicy(params) {
|
|
999
|
+
return {
|
|
1000
|
+
allowWrite: [params.sandboxRootPath],
|
|
1001
|
+
denyWrite: denySharedDefaultWritePaths(),
|
|
1002
|
+
denyRead: ["/"],
|
|
1003
|
+
allowRead: [params.sandboxRootPath, ...codeModeSocketParentAllow(), ...platformAllowRead(params.platform)]
|
|
1004
|
+
};
|
|
1005
|
+
}
|
|
1006
|
+
function commandEnv(params) {
|
|
1007
|
+
const tmp = join2(params.sandboxRootPath, ".tmp");
|
|
1008
|
+
const home = join2(params.sandboxRootPath, ".home");
|
|
1009
|
+
const venvDir = sandboxVenvPath(params.sandboxRootPath);
|
|
1010
|
+
const locked = {
|
|
1011
|
+
HOME: home,
|
|
1012
|
+
TMPDIR: tmp,
|
|
1013
|
+
TMP: tmp,
|
|
1014
|
+
TEMP: tmp,
|
|
1015
|
+
VIRTUAL_ENV: venvDir,
|
|
1016
|
+
// Cwd-relative layout dirs (not host-absolute). Absolute paths under
|
|
1017
|
+
// `Application Support` break PATH lookup (`mcp-client: command not found`).
|
|
1018
|
+
PATH: `${join2(SANDBOX_VENV_DIR, "bin")}:mcp-client/bin:${commandPath(params.platform)}`
|
|
1019
|
+
};
|
|
1020
|
+
return {
|
|
1021
|
+
...params.extra,
|
|
1022
|
+
...locked
|
|
1023
|
+
};
|
|
1024
|
+
}
|
|
1025
|
+
function sessionFilesystem(platform) {
|
|
1026
|
+
const allowWrite = [];
|
|
1027
|
+
return {
|
|
1028
|
+
allowWrite,
|
|
1029
|
+
denyWrite: denySharedDefaultWritePaths(),
|
|
1030
|
+
denyRead: ["/"],
|
|
1031
|
+
allowRead: [...platformAllowRead(platform), ...codeModeSocketParentAllow()]
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function sessionNetwork(params) {
|
|
1035
|
+
const allowedDomains = allowedNetworkDomains();
|
|
1036
|
+
const deniedDomains = [];
|
|
1037
|
+
if (params.platform === "linux") {
|
|
1038
|
+
return {
|
|
1039
|
+
allowedDomains,
|
|
1040
|
+
deniedDomains,
|
|
1041
|
+
allowAllUnixSockets: true
|
|
1042
|
+
};
|
|
1043
|
+
}
|
|
1044
|
+
return {
|
|
1045
|
+
allowedDomains,
|
|
1046
|
+
deniedDomains,
|
|
1047
|
+
allowAllUnixSockets: false,
|
|
1048
|
+
allowUnixSockets: params.unixSockets ?? []
|
|
1049
|
+
};
|
|
1050
|
+
}
|
|
1051
|
+
function darwinUnixSocketPaths() {
|
|
1052
|
+
return [...darwinUnixSocketSandboxRoots, ...codeModeSocketParentAllow()];
|
|
1053
|
+
}
|
|
1054
|
+
function syncDarwinUnixSockets() {
|
|
1055
|
+
if (SandboxManager.getConfig() === void 0) {
|
|
1056
|
+
return;
|
|
1057
|
+
}
|
|
1058
|
+
const platform = requireActivePlatform();
|
|
1059
|
+
if (platform !== "darwin") {
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
SandboxManager.updateConfig(buildSessionConfig(platform));
|
|
1063
|
+
}
|
|
1064
|
+
function buildSessionConfig(platform) {
|
|
1065
|
+
return {
|
|
1066
|
+
network: sessionNetwork({ platform, unixSockets: darwinUnixSocketPaths() }),
|
|
1067
|
+
filesystem: sessionFilesystem(platform)
|
|
1068
|
+
};
|
|
1069
|
+
}
|
|
1070
|
+
async function createSandbox(sandboxRootPath) {
|
|
1071
|
+
await mkdir3(sandboxRootPath, { recursive: true, mode: 448 });
|
|
1072
|
+
await mkdir3(join2(sandboxRootPath, ".tmp"), { recursive: true, mode: 448 });
|
|
1073
|
+
await mkdir3(join2(sandboxRootPath, ".home"), { recursive: true, mode: 448 });
|
|
1074
|
+
const realRoot = realpathSync2(sandboxRootPath);
|
|
1075
|
+
darwinUnixSocketSandboxRoots.add(realRoot);
|
|
1076
|
+
syncDarwinUnixSockets();
|
|
1077
|
+
return realRoot;
|
|
1078
|
+
}
|
|
1079
|
+
async function removeSandbox(sandboxRootPath) {
|
|
1080
|
+
darwinUnixSocketSandboxRoots.delete(sandboxRootPath);
|
|
1081
|
+
syncDarwinUnixSockets();
|
|
1082
|
+
await rm3(sandboxRootPath, { recursive: true, force: true });
|
|
1083
|
+
}
|
|
1084
|
+
async function initSrt(params) {
|
|
1085
|
+
activePlatform = params.platform;
|
|
1086
|
+
codeModeSocketParentPath = params.codeModeSocketParentPath === void 0 ? void 0 : realpathSync2(params.codeModeSocketParentPath);
|
|
1087
|
+
await SandboxManager.initialize(buildSessionConfig(params.platform));
|
|
1088
|
+
}
|
|
1089
|
+
async function resetSrt() {
|
|
1090
|
+
codeModeSocketParentPath = void 0;
|
|
1091
|
+
activePlatform = void 0;
|
|
1092
|
+
await SandboxManager.reset();
|
|
1093
|
+
}
|
|
1094
|
+
function isSrtInitialized() {
|
|
1095
|
+
return activePlatform !== void 0 && SandboxManager.getConfig() !== void 0;
|
|
1096
|
+
}
|
|
1097
|
+
function killExecTree(child) {
|
|
1098
|
+
if (!child) {
|
|
1099
|
+
return;
|
|
1100
|
+
}
|
|
1101
|
+
const pid = child.pid;
|
|
1102
|
+
if (pid !== void 0 && process.platform !== "win32") {
|
|
1103
|
+
try {
|
|
1104
|
+
process.kill(-pid, "SIGKILL");
|
|
1105
|
+
return;
|
|
1106
|
+
} catch {
|
|
1107
|
+
}
|
|
1108
|
+
}
|
|
1109
|
+
if (!child.killed) {
|
|
1110
|
+
child.kill("SIGKILL");
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
async function runSupervisorSession(params) {
|
|
1114
|
+
const {
|
|
1115
|
+
sandboxRootPath,
|
|
1116
|
+
command: rawCommand,
|
|
1117
|
+
shell,
|
|
1118
|
+
platform,
|
|
1119
|
+
cwd = sandboxRootPath,
|
|
1120
|
+
env,
|
|
1121
|
+
stdin,
|
|
1122
|
+
onChildSpawn,
|
|
1123
|
+
timeoutMs
|
|
1124
|
+
} = params;
|
|
1125
|
+
const command = wrapSandboxCommand({ platform, command: rawCommand });
|
|
1126
|
+
const wrap = await SandboxManager.wrapWithSandboxArgv(
|
|
1127
|
+
command,
|
|
1128
|
+
shell,
|
|
1129
|
+
{
|
|
1130
|
+
filesystem: filesystemPolicy({ sandboxRootPath, platform }),
|
|
1131
|
+
network: {
|
|
1132
|
+
allowedDomains: allowedNetworkDomains(),
|
|
1133
|
+
deniedDomains: []
|
|
1134
|
+
}
|
|
1135
|
+
},
|
|
1136
|
+
void 0,
|
|
1137
|
+
sandboxRootPath,
|
|
1138
|
+
{ commandId: randomUUID(), commandText: command }
|
|
1139
|
+
);
|
|
1140
|
+
const [argv0, ...argvRest] = wrap.argv;
|
|
1141
|
+
if (argv0 === void 0) {
|
|
1142
|
+
throw new Error("wrapWithSandboxArgv returned empty argv");
|
|
1143
|
+
}
|
|
1144
|
+
const childEnv = {
|
|
1145
|
+
...commandEnv({ sandboxRootPath, platform, ...env === void 0 ? {} : { extra: env } })
|
|
1146
|
+
};
|
|
1147
|
+
const child = spawn(argv0, argvRest, {
|
|
1148
|
+
cwd,
|
|
1149
|
+
env: childEnv,
|
|
1150
|
+
shell: false,
|
|
1151
|
+
// Detached process groups break stdin forwarding for upload (`cat` via pipe) under Jest.
|
|
1152
|
+
detached: stdin === void 0 && process.platform !== "win32",
|
|
1153
|
+
stdio: [stdin === void 0 ? "ignore" : "pipe", "pipe", "pipe"]
|
|
1154
|
+
});
|
|
1155
|
+
if (child.pid !== void 0) {
|
|
1156
|
+
onChildSpawn?.(child.pid);
|
|
1157
|
+
}
|
|
1158
|
+
if (stdin !== void 0) {
|
|
1159
|
+
const stdinStream = child.stdin;
|
|
1160
|
+
if (stdinStream === null) {
|
|
1161
|
+
killExecTree(child);
|
|
1162
|
+
SandboxManager.cleanupAfterCommand();
|
|
1163
|
+
throw new Error("stdin unavailable for sandboxed command");
|
|
1164
|
+
}
|
|
1165
|
+
stdinStream.on("error", () => void 0);
|
|
1166
|
+
await new Promise((resolve3, reject) => {
|
|
1167
|
+
stdinStream.end(stdin, (error) => {
|
|
1168
|
+
if (error) {
|
|
1169
|
+
reject(error);
|
|
1170
|
+
} else {
|
|
1171
|
+
resolve3();
|
|
1172
|
+
}
|
|
1173
|
+
});
|
|
1174
|
+
});
|
|
1175
|
+
}
|
|
1176
|
+
let stdoutText = "";
|
|
1177
|
+
let stderrText = "";
|
|
1178
|
+
let bufferedOutput = 0;
|
|
1179
|
+
let protocolError;
|
|
1180
|
+
let timedOut = false;
|
|
1181
|
+
let closed = false;
|
|
1182
|
+
const ignoreStreamError = (stream) => {
|
|
1183
|
+
stream?.on("error", () => void 0);
|
|
1184
|
+
};
|
|
1185
|
+
const appendOutput = (stream, chunk) => {
|
|
1186
|
+
bufferedOutput += chunk.length;
|
|
1187
|
+
if (bufferedOutput > MAX_OUTPUT_BYTES) {
|
|
1188
|
+
protocolError = `buffered output exceeded ${String(MAX_OUTPUT_BYTES)} bytes`;
|
|
1189
|
+
killExecTree(child);
|
|
1190
|
+
return;
|
|
1191
|
+
}
|
|
1192
|
+
const text = chunk.toString("utf8");
|
|
1193
|
+
if (stream === "stdout") {
|
|
1194
|
+
stdoutText += text;
|
|
1195
|
+
} else {
|
|
1196
|
+
stderrText += text;
|
|
1197
|
+
}
|
|
1198
|
+
};
|
|
1199
|
+
ignoreStreamError(child.stdout);
|
|
1200
|
+
ignoreStreamError(child.stderr);
|
|
1201
|
+
child.stdout?.on("data", (chunk) => {
|
|
1202
|
+
appendOutput("stdout", chunk);
|
|
1203
|
+
});
|
|
1204
|
+
child.stderr?.on("data", (chunk) => {
|
|
1205
|
+
appendOutput("stderr", chunk);
|
|
1206
|
+
});
|
|
1207
|
+
return await new Promise((resolve3, reject) => {
|
|
1208
|
+
const timer = setTimeout(() => {
|
|
1209
|
+
timedOut = true;
|
|
1210
|
+
killExecTree(child);
|
|
1211
|
+
}, timeoutMs);
|
|
1212
|
+
child.on("error", (error) => {
|
|
1213
|
+
if (closed) {
|
|
1214
|
+
return;
|
|
1215
|
+
}
|
|
1216
|
+
closed = true;
|
|
1217
|
+
clearTimeout(timer);
|
|
1218
|
+
SandboxManager.cleanupAfterCommand();
|
|
1219
|
+
reject(error);
|
|
1220
|
+
});
|
|
1221
|
+
child.on("close", (code) => {
|
|
1222
|
+
if (closed) {
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
closed = true;
|
|
1226
|
+
clearTimeout(timer);
|
|
1227
|
+
SandboxManager.cleanupAfterCommand();
|
|
1228
|
+
resolve3({
|
|
1229
|
+
stdoutText,
|
|
1230
|
+
stderrText,
|
|
1231
|
+
exitCode: typeof code === "number" ? code : timedOut ? 1 : 0,
|
|
1232
|
+
protocolError,
|
|
1233
|
+
timedOut,
|
|
1234
|
+
childPid: child.pid
|
|
1235
|
+
});
|
|
1236
|
+
});
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
var execFileAsync, SRT_VENDOR, MAX_OUTPUT_BYTES, activePlatform, codeModeSocketParentPath, COMMAND_PATH_BY_PLATFORM, SANDBOX_VENV_DIR, LOCAL_SANDBOX_ALLOWED_DOMAINS, SRT_HOST_BINARIES_BY_PLATFORM, ALLOW_READ_BY_PLATFORM, darwinUnixSocketSandboxRoots;
|
|
1240
|
+
var init_hostRun = __esm({
|
|
1241
|
+
"src/sandbox/local/core/hostRun.ts"() {
|
|
1242
|
+
"use strict";
|
|
1243
|
+
execFileAsync = promisify(execFile);
|
|
1244
|
+
SRT_VENDOR = join2(
|
|
1245
|
+
dirname2(createRequire(import.meta.url).resolve("@anthropic-ai/sandbox-runtime/package.json")),
|
|
1246
|
+
"vendor"
|
|
1247
|
+
);
|
|
1248
|
+
MAX_OUTPUT_BYTES = 14 * 1024 * 1024;
|
|
1249
|
+
COMMAND_PATH_BY_PLATFORM = {
|
|
1250
|
+
// On macOS, prefer Homebrew ahead of `/usr/bin` shims (those need Xcode select
|
|
1251
|
+
// paths that we intentionally do not allow-read).
|
|
1252
|
+
darwin: "/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin",
|
|
1253
|
+
linux: "/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin"
|
|
1254
|
+
};
|
|
1255
|
+
SANDBOX_VENV_DIR = ".venv";
|
|
1256
|
+
LOCAL_SANDBOX_ALLOWED_DOMAINS = [
|
|
1257
|
+
"pypi.org",
|
|
1258
|
+
"*.pypi.org",
|
|
1259
|
+
"pythonhosted.org",
|
|
1260
|
+
"files.pythonhosted.org",
|
|
1261
|
+
"*.pythonhosted.org",
|
|
1262
|
+
"github.com",
|
|
1263
|
+
"api.github.com",
|
|
1264
|
+
"codeload.github.com",
|
|
1265
|
+
"*.github.com",
|
|
1266
|
+
"githubusercontent.com",
|
|
1267
|
+
"raw.githubusercontent.com",
|
|
1268
|
+
"objects.githubusercontent.com",
|
|
1269
|
+
"*.githubusercontent.com"
|
|
1270
|
+
];
|
|
1271
|
+
SRT_HOST_BINARIES_BY_PLATFORM = {
|
|
1272
|
+
linux: ["bwrap", "socat", "rg"],
|
|
1273
|
+
darwin: []
|
|
1274
|
+
};
|
|
1275
|
+
ALLOW_READ_BY_PLATFORM = {
|
|
1276
|
+
darwin: [
|
|
1277
|
+
"/opt/homebrew/bin",
|
|
1278
|
+
"/usr/local",
|
|
1279
|
+
"/usr/bin",
|
|
1280
|
+
"/bin",
|
|
1281
|
+
"/usr/sbin",
|
|
1282
|
+
"/sbin",
|
|
1283
|
+
"/usr/lib",
|
|
1284
|
+
"/System/Library",
|
|
1285
|
+
"/Library",
|
|
1286
|
+
"/private/var/db/dyld",
|
|
1287
|
+
"/private/var/select",
|
|
1288
|
+
"/opt/homebrew",
|
|
1289
|
+
"/dev",
|
|
1290
|
+
// Darwin `/etc` is a symlink to `/private/etc`. SRT refuses that realpath as
|
|
1291
|
+
// "outside boundary" and keeps `(subpath "/etc")`. Seatbelt still checks the
|
|
1292
|
+
// resolved path, so Apple LibreSSL's fopen(`/private/etc/ssl/openssl.cnf`)
|
|
1293
|
+
// needs `/private/etc` listed too. Do not use a `[/]etc/**` glob: SRT treats
|
|
1294
|
+
// that as cwd-relative (`path.isAbsolute('[/]…')` is false).
|
|
1295
|
+
"/etc",
|
|
1296
|
+
"/private/etc"
|
|
1297
|
+
],
|
|
1298
|
+
linux: [
|
|
1299
|
+
"/usr/bin",
|
|
1300
|
+
"/bin",
|
|
1301
|
+
"/usr/sbin",
|
|
1302
|
+
"/sbin",
|
|
1303
|
+
"/lib",
|
|
1304
|
+
"/lib64",
|
|
1305
|
+
"/usr/lib",
|
|
1306
|
+
"/usr/lib64",
|
|
1307
|
+
"/usr/local",
|
|
1308
|
+
"/etc",
|
|
1309
|
+
"/dev",
|
|
1310
|
+
"/proc",
|
|
1311
|
+
"/sys",
|
|
1312
|
+
SRT_VENDOR
|
|
1313
|
+
]
|
|
1314
|
+
};
|
|
1315
|
+
darwinUnixSocketSandboxRoots = /* @__PURE__ */ new Set();
|
|
1316
|
+
}
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
// src/sandbox/local/schemas/xferFileInfo.ts
|
|
1320
|
+
import { z as z6 } from "zod";
|
|
1321
|
+
var XferFileInfoSchema;
|
|
1322
|
+
var init_xferFileInfo = __esm({
|
|
1323
|
+
"src/sandbox/local/schemas/xferFileInfo.ts"() {
|
|
1324
|
+
"use strict";
|
|
1325
|
+
XferFileInfoSchema = z6.object({
|
|
1326
|
+
size: z6.number(),
|
|
1327
|
+
isDir: z6.boolean()
|
|
1328
|
+
});
|
|
1329
|
+
}
|
|
1330
|
+
});
|
|
1331
|
+
|
|
1332
|
+
// src/sandbox/local/provider/LocalSandboxProvider.ts
|
|
1333
|
+
var LocalSandboxProvider_exports = {};
|
|
1334
|
+
__export(LocalSandboxProvider_exports, {
|
|
1335
|
+
LocalSandboxProvider: () => LocalSandboxProvider,
|
|
1336
|
+
formatLocalSandboxSupportReason: () => formatLocalSandboxSupportReason,
|
|
1337
|
+
localSandboxUploadCommand: () => localSandboxUploadCommand
|
|
1338
|
+
});
|
|
1339
|
+
import {
|
|
1340
|
+
absolutizeRelativeExecEnv,
|
|
1341
|
+
SandboxFileNotFoundError,
|
|
1342
|
+
SandboxFileTooLargeError,
|
|
1343
|
+
SandboxNotAvailableError,
|
|
1344
|
+
SandboxPathIsDirectoryError,
|
|
1345
|
+
shellEscape,
|
|
1346
|
+
validateNoPathTraversal as validateNoPathTraversal2
|
|
1347
|
+
} from "@truefoundry/trueforge-core/core";
|
|
1348
|
+
import { execFile as execFile2 } from "child_process";
|
|
1349
|
+
import { existsSync as existsSync4, realpathSync as realpathSync3, statSync as statSync2 } from "fs";
|
|
1350
|
+
import { mkdir as mkdir4, mkdtemp, rm as rm4 } from "fs/promises";
|
|
1351
|
+
import { tmpdir } from "os";
|
|
1352
|
+
import { dirname as dirname3, isAbsolute as isAbsolute3, join as join3, relative, resolve as resolve2, sep } from "path";
|
|
1353
|
+
import { promisify as promisify2 } from "util";
|
|
1354
|
+
import { ulid as ulid2 } from "ulid";
|
|
1355
|
+
function formatLocalSandboxSupportReason(params) {
|
|
1356
|
+
const details = params.attempts.map(formatLocalSandboxSupportAttempt).join("; ");
|
|
1357
|
+
return details.length === 0 ? params.summary : `${params.summary}: ${details}`;
|
|
1358
|
+
}
|
|
1359
|
+
function formatLocalSandboxSupportAttempt(attempt) {
|
|
1360
|
+
if (attempt.resolved === void 0) {
|
|
1361
|
+
return attempt.kind === "host" ? `${attempt.name}: not on PATH` : `${attempt.name}: not on sandbox PATH`;
|
|
1362
|
+
}
|
|
1363
|
+
const parts = [`${attempt.name}: resolved=${attempt.resolved}`];
|
|
1364
|
+
if (attempt.executable !== void 0 && attempt.executable !== attempt.resolved) {
|
|
1365
|
+
parts.push(`executable=${attempt.executable}`);
|
|
1366
|
+
}
|
|
1367
|
+
if (attempt.protocolError !== void 0) {
|
|
1368
|
+
parts.push(`protocolError=${attempt.protocolError}`);
|
|
1369
|
+
}
|
|
1370
|
+
if (attempt.exitCode !== void 0) {
|
|
1371
|
+
parts.push(`exit=${String(attempt.exitCode)}`);
|
|
1372
|
+
}
|
|
1373
|
+
if (attempt.timedOut === true) {
|
|
1374
|
+
parts.push("timedOut");
|
|
1375
|
+
}
|
|
1376
|
+
if (attempt.stderr !== void 0 && attempt.stderr.length > 0) {
|
|
1377
|
+
parts.push(`stderr=${JSON.stringify(attempt.stderr)}`);
|
|
1378
|
+
}
|
|
1379
|
+
if (attempt.stdout !== void 0 && attempt.stdout.length > 0) {
|
|
1380
|
+
parts.push(`stdout=${JSON.stringify(attempt.stdout)}`);
|
|
1381
|
+
}
|
|
1382
|
+
return parts.join(" ");
|
|
1383
|
+
}
|
|
1384
|
+
function probeAttemptFromSession(params) {
|
|
1385
|
+
return {
|
|
1386
|
+
kind: params.kind,
|
|
1387
|
+
name: params.name,
|
|
1388
|
+
resolved: params.resolved,
|
|
1389
|
+
...params.executable === void 0 ? {} : { executable: params.executable },
|
|
1390
|
+
exitCode: params.session.exitCode,
|
|
1391
|
+
stdout: params.session.stdoutText,
|
|
1392
|
+
stderr: params.session.stderrText,
|
|
1393
|
+
...params.session.protocolError === void 0 ? {} : { protocolError: params.session.protocolError },
|
|
1394
|
+
timedOut: params.session.timedOut
|
|
1395
|
+
};
|
|
1396
|
+
}
|
|
1397
|
+
function unsupported(params) {
|
|
1398
|
+
return {
|
|
1399
|
+
supported: false,
|
|
1400
|
+
reason: params.reason,
|
|
1401
|
+
...params.platform === void 0 ? {} : { platform: params.platform },
|
|
1402
|
+
...params.attempts === void 0 ? {} : { attempts: params.attempts }
|
|
1403
|
+
};
|
|
1404
|
+
}
|
|
1405
|
+
function toSandboxRelativePath(params) {
|
|
1406
|
+
const rel = relative(params.sandboxRootPath, params.absolutePath);
|
|
1407
|
+
return rel === "" ? "." : rel;
|
|
1408
|
+
}
|
|
1409
|
+
function isStrictDescendant(params) {
|
|
1410
|
+
const rel = relative(params.ancestor, params.descendant);
|
|
1411
|
+
return rel !== "" && rel !== ".." && !rel.startsWith(`..${sep}`) && !isAbsolute3(rel);
|
|
1412
|
+
}
|
|
1413
|
+
function execFileErrorDetail(error) {
|
|
1414
|
+
if (!(error instanceof Error)) {
|
|
1415
|
+
return String(error);
|
|
1416
|
+
}
|
|
1417
|
+
const parts = [error.message];
|
|
1418
|
+
if ("stderr" in error && typeof error.stderr === "string" && error.stderr.length > 0) {
|
|
1419
|
+
parts.push(error.stderr);
|
|
1420
|
+
}
|
|
1421
|
+
if ("stdout" in error && typeof error.stdout === "string" && error.stdout.length > 0) {
|
|
1422
|
+
parts.push(error.stdout);
|
|
1423
|
+
}
|
|
1424
|
+
return parts.join(" ");
|
|
1425
|
+
}
|
|
1426
|
+
function sessionFailDetail(session) {
|
|
1427
|
+
return [session.protocolError, session.stderrText, session.stdoutText].filter((part) => part !== void 0 && part.length > 0).join(" ");
|
|
1428
|
+
}
|
|
1429
|
+
function localSandboxUploadCommand(remotePath) {
|
|
1430
|
+
const parent = dirname3(remotePath);
|
|
1431
|
+
return [
|
|
1432
|
+
`mkdir -p ${shellEscape(parent)}`,
|
|
1433
|
+
`{ chmod u+w ${shellEscape(remotePath)} 2>/dev/null || true; }`,
|
|
1434
|
+
`/bin/cat > ${shellEscape(remotePath)}`
|
|
1435
|
+
].join(" && ");
|
|
1436
|
+
}
|
|
1437
|
+
var execFileAsync2, DEFAULT_EXEC_TIMEOUT_SECONDS, DEFAULT_FILE_MAX_BYTES, SUPPORT_PROBE_TIMEOUT_MS, VENV_CREATE_TIMEOUT_MS, VENV_PIP_TIMEOUT_MS, VENV_PYDANTIC_PIN, SHELL_CANDIDATES, PYTHON_CANDIDATES, LocalSandboxProvider;
|
|
1438
|
+
var init_LocalSandboxProvider = __esm({
|
|
1439
|
+
"src/sandbox/local/provider/LocalSandboxProvider.ts"() {
|
|
1440
|
+
"use strict";
|
|
1441
|
+
init_CodeModeUdsTransport();
|
|
1442
|
+
init_hostRun();
|
|
1443
|
+
init_xferFileInfo();
|
|
1444
|
+
execFileAsync2 = promisify2(execFile2);
|
|
1445
|
+
DEFAULT_EXEC_TIMEOUT_SECONDS = 60;
|
|
1446
|
+
DEFAULT_FILE_MAX_BYTES = 10 * 1024 * 1024;
|
|
1447
|
+
SUPPORT_PROBE_TIMEOUT_MS = 5e3;
|
|
1448
|
+
VENV_CREATE_TIMEOUT_MS = 6e4;
|
|
1449
|
+
VENV_PIP_TIMEOUT_MS = 12e4;
|
|
1450
|
+
VENV_PYDANTIC_PIN = "pydantic>=2.0.0,<3.0.0";
|
|
1451
|
+
SHELL_CANDIDATES = ["bash", "sh"];
|
|
1452
|
+
PYTHON_CANDIDATES = ["python3", "python"];
|
|
1453
|
+
LocalSandboxProvider = class _LocalSandboxProvider {
|
|
1454
|
+
type = "local";
|
|
1455
|
+
sandboxRootPathParent;
|
|
1456
|
+
codeModeSocketParentPath;
|
|
1457
|
+
support;
|
|
1458
|
+
fileMaxBytesForDownload;
|
|
1459
|
+
defaultExecTimeoutSeconds;
|
|
1460
|
+
srtInitialized = false;
|
|
1461
|
+
logger;
|
|
1462
|
+
/** Local SRT has no image build step — always ready. */
|
|
1463
|
+
static readyBuild = {
|
|
1464
|
+
status: "ready",
|
|
1465
|
+
reason: null,
|
|
1466
|
+
metadata: null
|
|
1467
|
+
};
|
|
1468
|
+
/**
|
|
1469
|
+
* Probe whether this host can run LocalSandboxProvider
|
|
1470
|
+
* (OS + Code Mode UDS listen + SRT host binaries + in-sandbox shell + Python 3).
|
|
1471
|
+
* On success, returns platform/shell/python to pass into the constructor as `support`.
|
|
1472
|
+
*/
|
|
1473
|
+
static async isSupported(params) {
|
|
1474
|
+
if (process.platform !== "darwin" && process.platform !== "linux") {
|
|
1475
|
+
return unsupported({
|
|
1476
|
+
reason: `LocalSandboxProvider supports macOS and Linux only (got ${process.platform})`
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
const platform = process.platform;
|
|
1480
|
+
let createdSocketParent;
|
|
1481
|
+
const socketParentPath = params?.codeModeSocketParentPath;
|
|
1482
|
+
try {
|
|
1483
|
+
const parentPath = socketParentPath ?? await mkdtemp(join3("/tmp", "tfc-"));
|
|
1484
|
+
if (socketParentPath === void 0) {
|
|
1485
|
+
createdSocketParent = parentPath;
|
|
1486
|
+
}
|
|
1487
|
+
try {
|
|
1488
|
+
await probeCodeModeUnixSocket(parentPath);
|
|
1489
|
+
} catch (error) {
|
|
1490
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1491
|
+
return unsupported({
|
|
1492
|
+
platform,
|
|
1493
|
+
attempts: [{ kind: "socket", name: "uds", resolved: parentPath, protocolError: message }],
|
|
1494
|
+
reason: `Code Mode UDS listen failed: ${message}`
|
|
1495
|
+
});
|
|
1496
|
+
}
|
|
1497
|
+
} finally {
|
|
1498
|
+
if (createdSocketParent !== void 0) {
|
|
1499
|
+
await rm4(createdSocketParent, { recursive: true, force: true });
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
const hostPath = process.env["PATH"];
|
|
1503
|
+
const hostAttempts = [];
|
|
1504
|
+
for (const name of srtHostBinaryNames(platform)) {
|
|
1505
|
+
const resolved = await resolveCommandOnHost({
|
|
1506
|
+
platform,
|
|
1507
|
+
name,
|
|
1508
|
+
...hostPath === void 0 || hostPath.length === 0 ? {} : { pathEnv: hostPath }
|
|
1509
|
+
});
|
|
1510
|
+
hostAttempts.push({ kind: "host", name, resolved });
|
|
1511
|
+
}
|
|
1512
|
+
if (hostAttempts.some((attempt) => attempt.resolved === void 0)) {
|
|
1513
|
+
const required = srtHostBinaryNames(platform).join(", ");
|
|
1514
|
+
return unsupported({
|
|
1515
|
+
platform,
|
|
1516
|
+
attempts: hostAttempts,
|
|
1517
|
+
reason: formatLocalSandboxSupportReason({
|
|
1518
|
+
summary: `SRT host dependencies missing (${platform}: ${required})`,
|
|
1519
|
+
attempts: hostAttempts
|
|
1520
|
+
})
|
|
1521
|
+
});
|
|
1522
|
+
}
|
|
1523
|
+
const srtDeps = await checkSrtHostDependencies();
|
|
1524
|
+
if (srtDeps.errors.length > 0) {
|
|
1525
|
+
return unsupported({
|
|
1526
|
+
platform,
|
|
1527
|
+
attempts: hostAttempts.length === 0 ? void 0 : hostAttempts,
|
|
1528
|
+
reason: `SRT host dependencies failed: ${srtDeps.errors.join("; ")}`
|
|
1529
|
+
});
|
|
1530
|
+
}
|
|
1531
|
+
const alreadyInitialized = isSrtInitialized();
|
|
1532
|
+
let probeRoot;
|
|
1533
|
+
const attempts = [...hostAttempts];
|
|
1534
|
+
const pythonAttempts = [];
|
|
1535
|
+
try {
|
|
1536
|
+
if (!alreadyInitialized) {
|
|
1537
|
+
await initSrt({
|
|
1538
|
+
platform,
|
|
1539
|
+
...socketParentPath === void 0 ? {} : { codeModeSocketParentPath: socketParentPath }
|
|
1540
|
+
});
|
|
1541
|
+
}
|
|
1542
|
+
probeRoot = await createSandbox(await mkdtemp(join3(tmpdir(), "tfy-local-sandbox-support-")));
|
|
1543
|
+
let shell;
|
|
1544
|
+
for (const name of SHELL_CANDIDATES) {
|
|
1545
|
+
const resolved = await resolveCommandOnHost({ platform, name });
|
|
1546
|
+
if (resolved === void 0) {
|
|
1547
|
+
attempts.push({ kind: "shell", name, resolved: void 0 });
|
|
1548
|
+
continue;
|
|
1549
|
+
}
|
|
1550
|
+
const probe = await runSupervisorSession({
|
|
1551
|
+
sandboxRootPath: probeRoot,
|
|
1552
|
+
platform,
|
|
1553
|
+
shell: resolved,
|
|
1554
|
+
command: "echo shell-ok",
|
|
1555
|
+
timeoutMs: SUPPORT_PROBE_TIMEOUT_MS
|
|
1556
|
+
});
|
|
1557
|
+
const attempt = probeAttemptFromSession({ kind: "shell", name, resolved, session: probe });
|
|
1558
|
+
if (probe.protocolError === void 0 && probe.exitCode === 0 && probe.stdoutText.includes("shell-ok")) {
|
|
1559
|
+
shell = resolved;
|
|
1560
|
+
break;
|
|
1561
|
+
}
|
|
1562
|
+
attempts.push(attempt);
|
|
1563
|
+
}
|
|
1564
|
+
if (shell === void 0) {
|
|
1565
|
+
return unsupported({
|
|
1566
|
+
platform,
|
|
1567
|
+
attempts,
|
|
1568
|
+
reason: formatLocalSandboxSupportReason({
|
|
1569
|
+
summary: "No usable shell in sandbox (bash or sh via command -v)",
|
|
1570
|
+
attempts
|
|
1571
|
+
})
|
|
1572
|
+
});
|
|
1573
|
+
}
|
|
1574
|
+
let python;
|
|
1575
|
+
for (const name of PYTHON_CANDIDATES) {
|
|
1576
|
+
const resolved = await resolveCommandOnHost({ platform, name });
|
|
1577
|
+
if (resolved === void 0) {
|
|
1578
|
+
pythonAttempts.push({ kind: "python", name, resolved: void 0 });
|
|
1579
|
+
continue;
|
|
1580
|
+
}
|
|
1581
|
+
const executable = await resolvePythonExecutableOnHost({ commandPath: resolved }) ?? resolved;
|
|
1582
|
+
const probe = await runSupervisorSession({
|
|
1583
|
+
sandboxRootPath: probeRoot,
|
|
1584
|
+
platform,
|
|
1585
|
+
shell,
|
|
1586
|
+
command: `${shellEscape(executable)} -c ${shellEscape(
|
|
1587
|
+
"import sys; raise SystemExit(0 if sys.version_info[0] == 3 else 1)"
|
|
1588
|
+
)}`,
|
|
1589
|
+
timeoutMs: SUPPORT_PROBE_TIMEOUT_MS
|
|
1590
|
+
});
|
|
1591
|
+
const attempt = probeAttemptFromSession({
|
|
1592
|
+
kind: "python",
|
|
1593
|
+
name,
|
|
1594
|
+
resolved,
|
|
1595
|
+
executable,
|
|
1596
|
+
session: probe
|
|
1597
|
+
});
|
|
1598
|
+
if (probe.protocolError === void 0 && probe.exitCode === 0) {
|
|
1599
|
+
python = executable;
|
|
1600
|
+
break;
|
|
1601
|
+
}
|
|
1602
|
+
pythonAttempts.push(attempt);
|
|
1603
|
+
}
|
|
1604
|
+
if (python === void 0) {
|
|
1605
|
+
return unsupported({
|
|
1606
|
+
platform,
|
|
1607
|
+
attempts: pythonAttempts,
|
|
1608
|
+
reason: formatLocalSandboxSupportReason({
|
|
1609
|
+
summary: "No usable Python 3 interpreter in sandbox (python3 or python via command -v)",
|
|
1610
|
+
attempts: pythonAttempts
|
|
1611
|
+
})
|
|
1612
|
+
});
|
|
1613
|
+
}
|
|
1614
|
+
return { supported: true, platform, shell, python };
|
|
1615
|
+
} catch (error) {
|
|
1616
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1617
|
+
const seen = [...attempts, ...pythonAttempts];
|
|
1618
|
+
return unsupported({
|
|
1619
|
+
platform,
|
|
1620
|
+
attempts: seen.length === 0 ? void 0 : seen,
|
|
1621
|
+
reason: seen.length === 0 ? message : `${message}: ${formatLocalSandboxSupportReason({ summary: "probe aborted", attempts: seen })}`
|
|
1622
|
+
});
|
|
1623
|
+
} finally {
|
|
1624
|
+
if (probeRoot !== void 0) {
|
|
1625
|
+
await removeSandbox(probeRoot);
|
|
1626
|
+
}
|
|
1627
|
+
if (!alreadyInitialized) {
|
|
1628
|
+
await resetSrt();
|
|
1629
|
+
}
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
constructor(options) {
|
|
1633
|
+
if (!options.support.supported) {
|
|
1634
|
+
throw new Error(`LocalSandboxProvider is not supported: ${options.support.reason}`);
|
|
1635
|
+
}
|
|
1636
|
+
if (!isAbsolute3(options.sandboxRootPathParent)) {
|
|
1637
|
+
throw new Error("sandboxRootPathParent must be an absolute path");
|
|
1638
|
+
}
|
|
1639
|
+
validateNoPathTraversal2(options.sandboxRootPathParent);
|
|
1640
|
+
const resolvedParent = resolve2(options.sandboxRootPathParent);
|
|
1641
|
+
this.sandboxRootPathParent = existsSync4(resolvedParent) && statSync2(resolvedParent).isDirectory() ? realpathSync3(resolvedParent) : resolvedParent;
|
|
1642
|
+
this.codeModeSocketParentPath = assertCodeModeSocketParentPath(options.codeModeSocketParentPath);
|
|
1643
|
+
this.support = options.support;
|
|
1644
|
+
this.fileMaxBytesForDownload = options.fileMaxBytesForDownload ?? DEFAULT_FILE_MAX_BYTES;
|
|
1645
|
+
this.defaultExecTimeoutSeconds = options.defaultExecTimeoutSeconds ?? DEFAULT_EXEC_TIMEOUT_SECONDS;
|
|
1646
|
+
this.logger = options.logger.child({ module: "LocalSandboxProvider" });
|
|
1647
|
+
}
|
|
1648
|
+
pythonC(code, relPath) {
|
|
1649
|
+
return `${this.support.python} -c ${shellEscape(code)} ${shellEscape(relPath)}`;
|
|
1650
|
+
}
|
|
1651
|
+
statCommand(relPath) {
|
|
1652
|
+
const code = [
|
|
1653
|
+
"import json, os, sys",
|
|
1654
|
+
"p = sys.argv[1]",
|
|
1655
|
+
"st = os.stat(p)",
|
|
1656
|
+
'print(json.dumps({"size": st.st_size, "isDir": os.path.isdir(p)}))'
|
|
1657
|
+
].join("\n");
|
|
1658
|
+
return this.pythonC(code, relPath);
|
|
1659
|
+
}
|
|
1660
|
+
base64EncodeCommand(relPath) {
|
|
1661
|
+
const code = [
|
|
1662
|
+
"import base64, sys",
|
|
1663
|
+
"p = sys.argv[1]",
|
|
1664
|
+
'sys.stdout.write(base64.b64encode(open(p, "rb").read()).decode("ascii"))'
|
|
1665
|
+
].join("\n");
|
|
1666
|
+
return this.pythonC(code, relPath);
|
|
1667
|
+
}
|
|
1668
|
+
buildImage() {
|
|
1669
|
+
return Promise.resolve(_LocalSandboxProvider.readyBuild);
|
|
1670
|
+
}
|
|
1671
|
+
getImageBuildStatus() {
|
|
1672
|
+
return Promise.resolve(_LocalSandboxProvider.readyBuild);
|
|
1673
|
+
}
|
|
1674
|
+
async ensureSrt() {
|
|
1675
|
+
if (this.srtInitialized) {
|
|
1676
|
+
return;
|
|
1677
|
+
}
|
|
1678
|
+
await initSrt({
|
|
1679
|
+
platform: this.support.platform,
|
|
1680
|
+
codeModeSocketParentPath: this.codeModeSocketParentPath
|
|
1681
|
+
});
|
|
1682
|
+
this.srtInitialized = true;
|
|
1683
|
+
}
|
|
1684
|
+
/** Missing, non-directory, or not a child of this provider's parent → recreate path in Sandbox. */
|
|
1685
|
+
ensureSandboxRoot(sandboxRootPath) {
|
|
1686
|
+
if (!isAbsolute3(sandboxRootPath)) {
|
|
1687
|
+
throw new SandboxNotAvailableError(sandboxRootPath);
|
|
1688
|
+
}
|
|
1689
|
+
const resolved = resolve2(sandboxRootPath);
|
|
1690
|
+
if (!existsSync4(resolved) || !statSync2(resolved).isDirectory()) {
|
|
1691
|
+
throw new SandboxNotAvailableError(sandboxRootPath);
|
|
1692
|
+
}
|
|
1693
|
+
const parentReal = existsSync4(this.sandboxRootPathParent) ? realpathSync3(this.sandboxRootPathParent) : this.sandboxRootPathParent;
|
|
1694
|
+
const realRoot = realpathSync3(resolved);
|
|
1695
|
+
if (!isStrictDescendant({ ancestor: parentReal, descendant: realRoot })) {
|
|
1696
|
+
throw new SandboxNotAvailableError(sandboxRootPath);
|
|
1697
|
+
}
|
|
1698
|
+
}
|
|
1699
|
+
/**
|
|
1700
|
+
* Host `python -m venv` (stdlib only), then sandboxed pip for pydantic.
|
|
1701
|
+
* Idempotent: skip create when `.venv/bin/python` exists; skip pip when import works.
|
|
1702
|
+
*/
|
|
1703
|
+
async ensureVenv(sandboxRootPath) {
|
|
1704
|
+
const venvDir = join3(sandboxRootPath, SANDBOX_VENV_DIR);
|
|
1705
|
+
const venvPython = join3(venvDir, "bin", "python");
|
|
1706
|
+
if (!existsSync4(venvPython)) {
|
|
1707
|
+
try {
|
|
1708
|
+
await execFileAsync2(this.support.python, ["-m", "venv", venvDir], { timeout: VENV_CREATE_TIMEOUT_MS });
|
|
1709
|
+
} catch (error) {
|
|
1710
|
+
throw new Error(`Failed to create sandbox ${SANDBOX_VENV_DIR}: ${execFileErrorDetail(error)}`, {
|
|
1711
|
+
cause: error
|
|
1712
|
+
});
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
const relPython = join3(SANDBOX_VENV_DIR, "bin", "python");
|
|
1716
|
+
const relPip = join3(SANDBOX_VENV_DIR, "bin", "pip");
|
|
1717
|
+
const check = await runSupervisorSession({
|
|
1718
|
+
sandboxRootPath,
|
|
1719
|
+
platform: this.support.platform,
|
|
1720
|
+
shell: this.support.shell,
|
|
1721
|
+
command: `${shellEscape(relPython)} -c ${shellEscape("import pydantic")}`,
|
|
1722
|
+
timeoutMs: SUPPORT_PROBE_TIMEOUT_MS
|
|
1723
|
+
});
|
|
1724
|
+
if (check.protocolError !== void 0) {
|
|
1725
|
+
throw new Error(`Sandbox venv pydantic check failed: ${check.protocolError}`);
|
|
1726
|
+
}
|
|
1727
|
+
if (check.exitCode === 0) {
|
|
1728
|
+
return;
|
|
1729
|
+
}
|
|
1730
|
+
const install = await runSupervisorSession({
|
|
1731
|
+
sandboxRootPath,
|
|
1732
|
+
platform: this.support.platform,
|
|
1733
|
+
shell: this.support.shell,
|
|
1734
|
+
command: `${shellEscape(relPip)} install --trusted-host pypi.org --trusted-host files.pythonhosted.org ${shellEscape(VENV_PYDANTIC_PIN)}`,
|
|
1735
|
+
timeoutMs: VENV_PIP_TIMEOUT_MS
|
|
1736
|
+
});
|
|
1737
|
+
if (install.protocolError !== void 0 || install.exitCode !== 0 || install.timedOut) {
|
|
1738
|
+
throw new Error(
|
|
1739
|
+
`Failed to pip install ${VENV_PYDANTIC_PIN} into sandbox ${SANDBOX_VENV_DIR}: ${sessionFailDetail(install)}`
|
|
1740
|
+
);
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
resolveInSandboxRoot(sandboxRootPath, userPath) {
|
|
1744
|
+
validateNoPathTraversal2(userPath);
|
|
1745
|
+
const resolved = userPath.startsWith("/") ? resolve2(userPath) : resolve2(sandboxRootPath, userPath);
|
|
1746
|
+
const root = resolve2(sandboxRootPath);
|
|
1747
|
+
if (resolved !== root && !resolved.startsWith(root + sep)) {
|
|
1748
|
+
throw new SandboxFileNotFoundError(userPath);
|
|
1749
|
+
}
|
|
1750
|
+
return resolved;
|
|
1751
|
+
}
|
|
1752
|
+
async runSandboxCommand(params) {
|
|
1753
|
+
const session = await runSupervisorSession({
|
|
1754
|
+
sandboxRootPath: params.sandboxRootPath,
|
|
1755
|
+
platform: this.support.platform,
|
|
1756
|
+
shell: this.support.shell,
|
|
1757
|
+
command: params.command,
|
|
1758
|
+
...params.stdin === void 0 ? {} : { stdin: params.stdin },
|
|
1759
|
+
timeoutMs: this.defaultExecTimeoutSeconds * 1e3
|
|
1760
|
+
});
|
|
1761
|
+
if (session.protocolError !== void 0) {
|
|
1762
|
+
throw new Error(session.protocolError);
|
|
1763
|
+
}
|
|
1764
|
+
return {
|
|
1765
|
+
exitCode: session.exitCode,
|
|
1766
|
+
stdoutText: session.stdoutText,
|
|
1767
|
+
stderrText: session.stderrText
|
|
1768
|
+
};
|
|
1769
|
+
}
|
|
1770
|
+
async getFileInfo(params) {
|
|
1771
|
+
const result = await this.runSandboxCommand({
|
|
1772
|
+
sandboxRootPath: params.sandboxRootPath,
|
|
1773
|
+
command: this.statCommand(params.relPath)
|
|
1774
|
+
});
|
|
1775
|
+
if (result.exitCode !== 0) {
|
|
1776
|
+
throw new SandboxFileNotFoundError(params.userPath);
|
|
1777
|
+
}
|
|
1778
|
+
return XferFileInfoSchema.parse(JSON.parse(result.stdoutText.trim()));
|
|
1779
|
+
}
|
|
1780
|
+
async createSandbox() {
|
|
1781
|
+
await this.ensureSrt();
|
|
1782
|
+
const sandboxId = await createSandbox(join3(this.sandboxRootPathParent, ulid2().toLowerCase()));
|
|
1783
|
+
this.logger.info("LocalSandboxProvider created sandbox", {
|
|
1784
|
+
sandboxId,
|
|
1785
|
+
shell: this.support.shell,
|
|
1786
|
+
python: this.support.python
|
|
1787
|
+
});
|
|
1788
|
+
await mkdir4(join3(sandboxId, this.getToolResultDumpDir()), { recursive: true, mode: 448 });
|
|
1789
|
+
await mkdir4(join3(sandboxId, this.getFileUploadsDir()), { recursive: true, mode: 448 });
|
|
1790
|
+
await mkdir4(join3(sandboxId, this.getSkillsDir()), { recursive: true, mode: 448 });
|
|
1791
|
+
await this.ensureVenv(sandboxId);
|
|
1792
|
+
return { sandboxId };
|
|
1793
|
+
}
|
|
1794
|
+
async exec(params) {
|
|
1795
|
+
this.ensureSandboxRoot(params.sandboxId);
|
|
1796
|
+
try {
|
|
1797
|
+
await this.ensureSrt();
|
|
1798
|
+
await this.ensureVenv(params.sandboxId);
|
|
1799
|
+
const sandboxRootPath = params.sandboxId;
|
|
1800
|
+
const cwd = params.cwd === void 0 || params.cwd === "" ? sandboxRootPath : this.resolveInSandboxRoot(sandboxRootPath, params.cwd);
|
|
1801
|
+
const timeoutSeconds = params.timeoutSeconds ?? this.defaultExecTimeoutSeconds;
|
|
1802
|
+
const env = params.env === void 0 ? void 0 : absolutizeRelativeExecEnv({ root: sandboxRootPath, env: params.env });
|
|
1803
|
+
const session = await runSupervisorSession({
|
|
1804
|
+
sandboxRootPath,
|
|
1805
|
+
platform: this.support.platform,
|
|
1806
|
+
shell: this.support.shell,
|
|
1807
|
+
command: params.command,
|
|
1808
|
+
cwd,
|
|
1809
|
+
...env === void 0 ? {} : { env },
|
|
1810
|
+
timeoutMs: timeoutSeconds * 1e3
|
|
1811
|
+
});
|
|
1812
|
+
if (session.protocolError !== void 0) {
|
|
1813
|
+
return { success: false, error: session.protocolError };
|
|
1814
|
+
}
|
|
1815
|
+
const result = session.stdoutText + (session.stderrText ? session.stderrText : "");
|
|
1816
|
+
return {
|
|
1817
|
+
success: true,
|
|
1818
|
+
response: { exitCode: session.exitCode, result }
|
|
1819
|
+
};
|
|
1820
|
+
} catch (error) {
|
|
1821
|
+
if (error instanceof SandboxNotAvailableError) {
|
|
1822
|
+
throw error;
|
|
1823
|
+
}
|
|
1824
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1825
|
+
return { success: false, error: message };
|
|
1826
|
+
}
|
|
1827
|
+
}
|
|
1828
|
+
getAdditionalInstructions() {
|
|
1829
|
+
return [
|
|
1830
|
+
"SANDBOX RULES:",
|
|
1831
|
+
`- Platform: ${this.support.platform}.`,
|
|
1832
|
+
`- Commands run under the sandbox shell: ${this.support.shell}.`,
|
|
1833
|
+
"- A Python virtualenv lives at .venv. `python` and `pip` on PATH are that environment; packages you `pip install` persist in this sandbox.",
|
|
1834
|
+
"- uploads, skills, and tool-results live in the sandbox working directory.",
|
|
1835
|
+
"- ALL file creation and writes MUST stay within the sandbox working directory.",
|
|
1836
|
+
"- The Agent must NOT write outside the working directory (including host home and /tmp)."
|
|
1837
|
+
].join("\n");
|
|
1838
|
+
}
|
|
1839
|
+
// Cwd-relative: SRT cwd is the sandbox root. Init mkdir/ln and the agent prompt must not use
|
|
1840
|
+
// host-absolute paths — those contain spaces under macOS Application Support and lose quoting.
|
|
1841
|
+
// uploads, skills, tool-results, git_downloader.py, .git-credentials, .venv
|
|
1842
|
+
// mcp-client/mcp_client.py
|
|
1843
|
+
getToolResultDumpDir() {
|
|
1844
|
+
return "tool-results";
|
|
1845
|
+
}
|
|
1846
|
+
getGitCredentialsPath() {
|
|
1847
|
+
return ".git-credentials";
|
|
1848
|
+
}
|
|
1849
|
+
getFileUploadsDir() {
|
|
1850
|
+
return "uploads";
|
|
1851
|
+
}
|
|
1852
|
+
getSkillsDir() {
|
|
1853
|
+
return "skills";
|
|
1854
|
+
}
|
|
1855
|
+
getGitDownloaderPath() {
|
|
1856
|
+
return "git_downloader.py";
|
|
1857
|
+
}
|
|
1858
|
+
async downloadFile(params) {
|
|
1859
|
+
this.ensureSandboxRoot(params.sandboxId);
|
|
1860
|
+
await this.ensureSrt();
|
|
1861
|
+
await this.ensureVenv(params.sandboxId);
|
|
1862
|
+
const sandboxRootPath = params.sandboxId;
|
|
1863
|
+
const absolutePath = this.resolveInSandboxRoot(sandboxRootPath, params.path);
|
|
1864
|
+
const relPath = toSandboxRelativePath({ sandboxRootPath, absolutePath });
|
|
1865
|
+
const info = await this.getFileInfo({ sandboxRootPath, relPath, userPath: params.path });
|
|
1866
|
+
if (info.isDir) {
|
|
1867
|
+
throw new SandboxPathIsDirectoryError(params.path);
|
|
1868
|
+
}
|
|
1869
|
+
if (info.size > this.fileMaxBytesForDownload) {
|
|
1870
|
+
throw new SandboxFileTooLargeError(params.path, info.size, this.fileMaxBytesForDownload);
|
|
1871
|
+
}
|
|
1872
|
+
const result = await this.runSandboxCommand({
|
|
1873
|
+
sandboxRootPath,
|
|
1874
|
+
command: this.base64EncodeCommand(relPath)
|
|
1875
|
+
});
|
|
1876
|
+
if (result.exitCode !== 0) {
|
|
1877
|
+
throw new SandboxFileNotFoundError(params.path);
|
|
1878
|
+
}
|
|
1879
|
+
const buf = Buffer.from(result.stdoutText.trim(), "base64");
|
|
1880
|
+
if (buf.length > this.fileMaxBytesForDownload) {
|
|
1881
|
+
throw new SandboxFileTooLargeError(params.path, buf.length, this.fileMaxBytesForDownload);
|
|
1882
|
+
}
|
|
1883
|
+
return buf;
|
|
1884
|
+
}
|
|
1885
|
+
/** Payload on stdin so large uploads stay off argv. */
|
|
1886
|
+
async uploadFile(params) {
|
|
1887
|
+
this.ensureSandboxRoot(params.sandboxId);
|
|
1888
|
+
await this.ensureSrt();
|
|
1889
|
+
await this.ensureVenv(params.sandboxId);
|
|
1890
|
+
if (params.content.length > this.fileMaxBytesForDownload) {
|
|
1891
|
+
throw new SandboxFileTooLargeError(params.remotePath, params.content.length, this.fileMaxBytesForDownload);
|
|
1892
|
+
}
|
|
1893
|
+
const sandboxRootPath = params.sandboxId;
|
|
1894
|
+
const absolutePath = this.resolveInSandboxRoot(sandboxRootPath, params.remotePath);
|
|
1895
|
+
const remotePath = toSandboxRelativePath({ sandboxRootPath, absolutePath });
|
|
1896
|
+
const result = await this.runSandboxCommand({
|
|
1897
|
+
sandboxRootPath,
|
|
1898
|
+
command: localSandboxUploadCommand(remotePath),
|
|
1899
|
+
stdin: params.content
|
|
1900
|
+
});
|
|
1901
|
+
if (result.exitCode !== 0) {
|
|
1902
|
+
const detail = [result.stderrText, result.stdoutText].filter((part) => part.length > 0).join(" ");
|
|
1903
|
+
throw new Error(
|
|
1904
|
+
detail.length > 0 ? `upload ${params.remotePath} failed (exit ${String(result.exitCode)}): ${detail}` : `upload ${params.remotePath} failed (exit ${String(result.exitCode)})`
|
|
1905
|
+
);
|
|
1906
|
+
}
|
|
1907
|
+
}
|
|
1908
|
+
createCodeModeTransport() {
|
|
1909
|
+
return new CodeModeUdsTransport({
|
|
1910
|
+
codeModeSocketParentPath: this.codeModeSocketParentPath,
|
|
1911
|
+
clientRemotePath: () => join3("mcp-client", "mcp_client.py")
|
|
1912
|
+
});
|
|
1913
|
+
}
|
|
1914
|
+
/** Reset process-scoped SRT for this provider. */
|
|
1915
|
+
async dispose() {
|
|
1916
|
+
if (this.srtInitialized) {
|
|
1917
|
+
await resetSrt();
|
|
1918
|
+
this.srtInitialized = false;
|
|
1919
|
+
}
|
|
1920
|
+
}
|
|
1921
|
+
};
|
|
333
1922
|
}
|
|
334
1923
|
});
|
|
335
1924
|
|
|
@@ -380,14 +1969,14 @@ function toStoredOAuthPendingAuthorizationData(pending) {
|
|
|
380
1969
|
return {
|
|
381
1970
|
mcp_server_url: pending.mcpServerUrl,
|
|
382
1971
|
code_verifier: pending.codeVerifier,
|
|
383
|
-
|
|
1972
|
+
return_to: pending.returnTo
|
|
384
1973
|
};
|
|
385
1974
|
}
|
|
386
1975
|
function fromStoredOAuthPendingAuthorizationData(stored) {
|
|
387
1976
|
return {
|
|
388
1977
|
mcpServerUrl: stored.mcp_server_url,
|
|
389
1978
|
codeVerifier: stored.code_verifier,
|
|
390
|
-
|
|
1979
|
+
returnTo: stored.return_to
|
|
391
1980
|
};
|
|
392
1981
|
}
|
|
393
1982
|
var McpServerNameConflictError, PENDING_AUTHORIZATION_TTL_MS;
|
|
@@ -740,10 +2329,10 @@ async function assembleTurnRecord(db, args) {
|
|
|
740
2329
|
}
|
|
741
2330
|
const contextRows = await db.selectFrom("turn_thread as tt").leftJoin(
|
|
742
2331
|
"turn_thread_context as ttc",
|
|
743
|
-
(
|
|
2332
|
+
(join5) => join5.on("ttc.session_id", "=", args.session_id).on("ttc.turn_id", "=", args.turn_id).onRef("ttc.thread_id", "=", "tt.thread_id")
|
|
744
2333
|
).leftJoin(
|
|
745
2334
|
"thread_context_log as l",
|
|
746
|
-
(
|
|
2335
|
+
(join5) => join5.on("l.session_id", "=", args.session_id).onRef("l.thread_id", "=", "tt.thread_id").onRef("l.append_id", "=", "ttc.append_id")
|
|
747
2336
|
).select([
|
|
748
2337
|
"tt.thread_id",
|
|
749
2338
|
jsonText(sql2.ref("tt.checkpoint")).as("checkpoint"),
|
|
@@ -840,10 +2429,10 @@ async function createTurn(db, input) {
|
|
|
840
2429
|
if (prevTurnId != null) {
|
|
841
2430
|
const prevRows = await trx.selectFrom("turn as t").leftJoin(
|
|
842
2431
|
"turn_thread as tt",
|
|
843
|
-
(
|
|
2432
|
+
(join5) => join5.onRef("tt.session_id", "=", "t.session_id").onRef("tt.turn_id", "=", "t.turn_id")
|
|
844
2433
|
).leftJoin(
|
|
845
2434
|
db.selectFrom("turn_thread_context").select(["thread_id", "turn_id", sql2`MAX(pos)`.as("max_pos")]).where("session_id", "=", input.session_id).where("turn_id", "=", prevTurnId).groupBy(["thread_id", "turn_id"]).as("tc_agg"),
|
|
846
|
-
(
|
|
2435
|
+
(join5) => join5.onRef("tc_agg.thread_id", "=", "tt.thread_id").onRef("tc_agg.turn_id", "=", "tt.turn_id")
|
|
847
2436
|
).select([
|
|
848
2437
|
jsonText(sql2.ref("t.checkpoint")).as("turn_checkpoint"),
|
|
849
2438
|
jsonText(sql2.ref("t.state")).as("turn_state"),
|
|
@@ -1212,7 +2801,7 @@ async function listTurnEvents(db, input) {
|
|
|
1212
2801
|
const eventOrder = input.order === "desc" ? "desc" : "asc";
|
|
1213
2802
|
const rows = await db.selectFrom("turn as t").leftJoin(
|
|
1214
2803
|
(eb) => eb.selectFrom("session_event").select(["session_id", "turn_id", "event_id", jsonText(sql4.ref("event")).as("event")]).where("session_id", "=", input.session_id).where("turn_id", "=", input.turn_id).orderBy("event_id", eventOrder).limit(limit + 1).offset(offset).as("e"),
|
|
1215
|
-
(
|
|
2804
|
+
(join5) => join5.onRef("e.session_id", "=", "t.session_id").onRef("e.turn_id", "=", "t.turn_id")
|
|
1216
2805
|
).select(["t.turn_id", "e.event"]).where("t.session_id", "=", input.session_id).where("t.turn_id", "=", input.turn_id).orderBy("e.event_id", eventOrder).execute();
|
|
1217
2806
|
if (rows.length === 0) {
|
|
1218
2807
|
throw new TurnNotFoundError3(input.turn_id);
|
|
@@ -1272,7 +2861,7 @@ async function listSessionEvents(db, input) {
|
|
|
1272
2861
|
const chainJson = JSON.stringify(chainIds);
|
|
1273
2862
|
const rows = await db.selectFrom(sql4`json_each(${chainJson})`.as("c")).innerJoin(
|
|
1274
2863
|
"session_event as e",
|
|
1275
|
-
(
|
|
2864
|
+
(join5) => join5.on("e.session_id", "=", input.session_id).on(sql4`e.turn_id = c.value`)
|
|
1276
2865
|
).select([sql4`c.value`.as("turn_id"), jsonText(sql4.ref("e.event")).as("event")]).orderBy(sql4`c.key`, "desc").orderBy("e.event_id", "desc").limit(limit + 1).offset(cursor.offset).execute();
|
|
1277
2866
|
return paginateSessionEventRows(rows, limit, cursor);
|
|
1278
2867
|
}
|
|
@@ -1958,7 +3547,7 @@ var SqliteMcpServerStore_exports = {};
|
|
|
1958
3547
|
__export(SqliteMcpServerStore_exports, {
|
|
1959
3548
|
SqliteMcpServerStore: () => SqliteMcpServerStore
|
|
1960
3549
|
});
|
|
1961
|
-
import { ulid as
|
|
3550
|
+
import { ulid as ulid5 } from "ulid";
|
|
1962
3551
|
function recordColumns2(eb) {
|
|
1963
3552
|
return [
|
|
1964
3553
|
"id",
|
|
@@ -2008,7 +3597,7 @@ var init_SqliteMcpServerStore = __esm({
|
|
|
2008
3597
|
const timestamp = nowIso();
|
|
2009
3598
|
try {
|
|
2010
3599
|
return await db.insertInto("mcp_server").values({
|
|
2011
|
-
id:
|
|
3600
|
+
id: ulid5(),
|
|
2012
3601
|
tenant_id: input.tenant_id,
|
|
2013
3602
|
name: input.name,
|
|
2014
3603
|
manifest: jsonbBind(input.manifest),
|
|
@@ -2028,7 +3617,7 @@ var init_SqliteMcpServerStore = __esm({
|
|
|
2028
3617
|
const db = transaction ?? this.#db;
|
|
2029
3618
|
const timestamp = nowIso();
|
|
2030
3619
|
return await db.insertInto("mcp_server").values({
|
|
2031
|
-
id:
|
|
3620
|
+
id: ulid5(),
|
|
2032
3621
|
tenant_id: input.tenant_id,
|
|
2033
3622
|
name: input.name,
|
|
2034
3623
|
manifest: jsonbBind(input.manifest),
|
|
@@ -2358,7 +3947,7 @@ var SqliteAgentStore_exports = {};
|
|
|
2358
3947
|
__export(SqliteAgentStore_exports, {
|
|
2359
3948
|
SqliteAgentStore: () => SqliteAgentStore
|
|
2360
3949
|
});
|
|
2361
|
-
import { ulid as
|
|
3950
|
+
import { ulid as ulid6 } from "ulid";
|
|
2362
3951
|
function recordColumns5(eb) {
|
|
2363
3952
|
return [
|
|
2364
3953
|
"id",
|
|
@@ -2405,7 +3994,7 @@ var init_SqliteAgentStore = __esm({
|
|
|
2405
3994
|
const timestamp = nowIso();
|
|
2406
3995
|
try {
|
|
2407
3996
|
const row = await db.insertInto("agent").values({
|
|
2408
|
-
id:
|
|
3997
|
+
id: ulid6().toLowerCase(),
|
|
2409
3998
|
tenant_id: input.tenant_id,
|
|
2410
3999
|
name: input.name,
|
|
2411
4000
|
manifest: jsonbBind(input.manifest),
|
|
@@ -2669,9 +4258,9 @@ async function assembleTurnRecord2(db, args) {
|
|
|
2669
4258
|
if (!turn) {
|
|
2670
4259
|
return void 0;
|
|
2671
4260
|
}
|
|
2672
|
-
const contextRows = await db.selectFrom("turn_thread as tt").leftJoin(lateralUnnestBigintArrayWithOrdinality(sql11`tt.context_ids`, "c"), (
|
|
4261
|
+
const contextRows = await db.selectFrom("turn_thread as tt").leftJoin(lateralUnnestBigintArrayWithOrdinality(sql11`tt.context_ids`, "c"), (join5) => join5.onTrue()).leftJoin(
|
|
2673
4262
|
"thread_context_log as l",
|
|
2674
|
-
(
|
|
4263
|
+
(join5) => join5.on("l.session_id", "=", args.session_id).onRef("l.thread_id", "=", "tt.thread_id").onRef("l.append_id", "=", "c.append_id")
|
|
2675
4264
|
).select(["tt.thread_id", "tt.checkpoint", "tt.agent_info", "tt.current_context_usage", "l.body", "c.pos"]).where("tt.session_id", "=", args.session_id).where("tt.turn_id", "=", args.turn_id).orderBy("tt.thread_id").orderBy("c.pos").execute();
|
|
2676
4265
|
const capabilityRows = await db.selectFrom("thread_capability_state").select(["thread_id", sql11`jsonb_object_agg(key, state)`.as("capability_state")]).where("session_id", "=", args.session_id).where("turn_id", "=", args.turn_id).groupBy("thread_id").execute();
|
|
2677
4266
|
const capabilityByThread = /* @__PURE__ */ new Map();
|
|
@@ -2754,7 +4343,7 @@ async function createTurn2(db, input) {
|
|
|
2754
4343
|
if (prevTurnId != null) {
|
|
2755
4344
|
const prevRows = await trx.selectFrom("turn as t").leftJoin(
|
|
2756
4345
|
"turn_thread as tt",
|
|
2757
|
-
(
|
|
4346
|
+
(join5) => join5.onRef("tt.session_id", "=", "t.session_id").onRef("tt.turn_id", "=", "t.turn_id")
|
|
2758
4347
|
).select([
|
|
2759
4348
|
"t.checkpoint as turn_checkpoint",
|
|
2760
4349
|
"t.state as turn_state",
|
|
@@ -3076,7 +4665,7 @@ async function listTurnEvents2(db, input) {
|
|
|
3076
4665
|
const eventOrder = input.order === "desc" ? "desc" : "asc";
|
|
3077
4666
|
const rows = await db.selectFrom("turn as t").leftJoin(
|
|
3078
4667
|
(eb) => eb.selectFrom("session_event").select(["session_id", "turn_id", "event_id", "event"]).where("session_id", "=", input.session_id).where("turn_id", "=", input.turn_id).orderBy("event_id", eventOrder).limit(limit + 1).offset(offset).as("e"),
|
|
3079
|
-
(
|
|
4668
|
+
(join5) => join5.onRef("e.session_id", "=", "t.session_id").onRef("e.turn_id", "=", "t.turn_id")
|
|
3080
4669
|
).select(["t.turn_id", "e.event"]).where("t.session_id", "=", input.session_id).where("t.turn_id", "=", input.turn_id).orderBy("e.event_id", eventOrder).execute();
|
|
3081
4670
|
if (rows.length === 0) {
|
|
3082
4671
|
throw new TurnNotFoundError5(input.turn_id);
|
|
@@ -3111,7 +4700,7 @@ async function listSessionEvents2(db, input) {
|
|
|
3111
4700
|
const chainIds = await resolveAncestorChain2(db, input.session_id, anchor);
|
|
3112
4701
|
const rows = await db.selectFrom(unnestWithOrdinality(chainIds, "c")).innerJoin(
|
|
3113
4702
|
"session_event as e",
|
|
3114
|
-
(
|
|
4703
|
+
(join5) => join5.on("e.session_id", "=", input.session_id).onRef("e.turn_id", "=", "c.turn_id")
|
|
3115
4704
|
).select(["e.turn_id as turn_id", "e.event as event"]).orderBy("c.pos", "desc").orderBy("e.event_id", "desc").limit(limit + 1).offset(cursor.offset).execute();
|
|
3116
4705
|
return paginateSessionEventRows2(rows, limit, cursor);
|
|
3117
4706
|
}
|
|
@@ -3751,7 +5340,7 @@ var PostgresMcpServerStore_exports = {};
|
|
|
3751
5340
|
__export(PostgresMcpServerStore_exports, {
|
|
3752
5341
|
PostgresMcpServerStore: () => PostgresMcpServerStore
|
|
3753
5342
|
});
|
|
3754
|
-
import { ulid as
|
|
5343
|
+
import { ulid as ulid7 } from "ulid";
|
|
3755
5344
|
function toRecord3(row) {
|
|
3756
5345
|
return {
|
|
3757
5346
|
id: row.id,
|
|
@@ -3799,7 +5388,7 @@ var init_PostgresMcpServerStore = __esm({
|
|
|
3799
5388
|
const db = transaction ?? this.#db;
|
|
3800
5389
|
try {
|
|
3801
5390
|
const row = await db.insertInto("mcp_server").values({
|
|
3802
|
-
id:
|
|
5391
|
+
id: ulid7(),
|
|
3803
5392
|
tenant_id: input.tenant_id,
|
|
3804
5393
|
name: input.name,
|
|
3805
5394
|
manifest: json(input.manifest),
|
|
@@ -3819,7 +5408,7 @@ var init_PostgresMcpServerStore = __esm({
|
|
|
3819
5408
|
async upsertServer(input, transaction) {
|
|
3820
5409
|
const db = transaction ?? this.#db;
|
|
3821
5410
|
const row = await db.insertInto("mcp_server").values({
|
|
3822
|
-
id:
|
|
5411
|
+
id: ulid7(),
|
|
3823
5412
|
tenant_id: input.tenant_id,
|
|
3824
5413
|
name: input.name,
|
|
3825
5414
|
manifest: json(input.manifest),
|
|
@@ -4142,7 +5731,7 @@ var PostgresAgentStore_exports = {};
|
|
|
4142
5731
|
__export(PostgresAgentStore_exports, {
|
|
4143
5732
|
PostgresAgentStore: () => PostgresAgentStore
|
|
4144
5733
|
});
|
|
4145
|
-
import { ulid as
|
|
5734
|
+
import { ulid as ulid8 } from "ulid";
|
|
4146
5735
|
function toRecord6(row) {
|
|
4147
5736
|
return {
|
|
4148
5737
|
id: row.id,
|
|
@@ -4185,7 +5774,7 @@ var init_PostgresAgentStore = __esm({
|
|
|
4185
5774
|
const db = transaction ?? this.#db;
|
|
4186
5775
|
try {
|
|
4187
5776
|
const row = await db.insertInto("agent").values({
|
|
4188
|
-
id:
|
|
5777
|
+
id: ulid8().toLowerCase(),
|
|
4189
5778
|
tenant_id: input.tenant_id,
|
|
4190
5779
|
name: input.name,
|
|
4191
5780
|
manifest: json(input.manifest),
|
|
@@ -4218,8 +5807,41 @@ var init_PostgresAgentStore = __esm({
|
|
|
4218
5807
|
|
|
4219
5808
|
// src/main.ts
|
|
4220
5809
|
import { extractErrorLogFields as extractErrorLogFields9 } from "@truefoundry/trueforge-core/core";
|
|
4221
|
-
import { mkdir } from "fs/promises";
|
|
5810
|
+
import { mkdir as mkdir5 } from "fs/promises";
|
|
4222
5811
|
import path6 from "path";
|
|
5812
|
+
|
|
5813
|
+
// src/sandbox/localLifecycle.ts
|
|
5814
|
+
import { existsSync } from "fs";
|
|
5815
|
+
import { mkdir, rm } from "fs/promises";
|
|
5816
|
+
var SOCKET_PARENT_MODE = 448;
|
|
5817
|
+
async function prepareCodeModeSocketParent(params) {
|
|
5818
|
+
if (existsSync(params.path)) {
|
|
5819
|
+
params.logger.warn("Removing leftover Code Mode socket parent", { path: params.path });
|
|
5820
|
+
await rm(params.path, { recursive: true, force: true });
|
|
5821
|
+
}
|
|
5822
|
+
await mkdir(params.path, { recursive: true, mode: SOCKET_PARENT_MODE });
|
|
5823
|
+
}
|
|
5824
|
+
async function removeCodeModeSocketParent(socketParentPath) {
|
|
5825
|
+
await rm(socketParentPath, { recursive: true, force: true });
|
|
5826
|
+
}
|
|
5827
|
+
async function ensureLocalSandboxRootParent(sandboxRootPathParent) {
|
|
5828
|
+
await mkdir(sandboxRootPathParent, { recursive: true, mode: SOCKET_PARENT_MODE });
|
|
5829
|
+
}
|
|
5830
|
+
|
|
5831
|
+
// src/sandbox/localRuntime.ts
|
|
5832
|
+
init_config();
|
|
5833
|
+
var cachedSupport;
|
|
5834
|
+
function setCachedLocalSandboxSupport(support) {
|
|
5835
|
+
cachedSupport = support;
|
|
5836
|
+
}
|
|
5837
|
+
function getCachedLocalSandboxSupport() {
|
|
5838
|
+
return cachedSupport;
|
|
5839
|
+
}
|
|
5840
|
+
function isLocalSandboxFallbackEnabled() {
|
|
5841
|
+
return config_default.STANDALONE && cachedSupport?.supported === true;
|
|
5842
|
+
}
|
|
5843
|
+
|
|
5844
|
+
// src/main.ts
|
|
4223
5845
|
import { serve } from "@hono/node-server";
|
|
4224
5846
|
import {
|
|
4225
5847
|
CancellationReason as CancellationReason4,
|
|
@@ -4229,7 +5851,7 @@ import { RequestReplyExecutor, RequestReplyRouter } from "@truefoundry/trueforge
|
|
|
4229
5851
|
|
|
4230
5852
|
// src/app.ts
|
|
4231
5853
|
import { swaggerUI } from "@hono/swagger-ui";
|
|
4232
|
-
import { OpenAPIHono as OpenAPIHono14, z as
|
|
5854
|
+
import { OpenAPIHono as OpenAPIHono14, z as z26 } from "@hono/zod-openapi";
|
|
4233
5855
|
import { extractErrorLogFields as extractErrorLogFields7 } from "@truefoundry/trueforge-core/core";
|
|
4234
5856
|
import { bodyLimit } from "hono/body-limit";
|
|
4235
5857
|
import { HTTPException as HTTPException5 } from "hono/http-exception";
|
|
@@ -4251,6 +5873,8 @@ var NameSchema = z.string().min(2).max(64).regex(
|
|
|
4251
5873
|
/^[a-z](?:[a-z0-9._-]{0,62}[a-z0-9])$/,
|
|
4252
5874
|
'must be 2\u201364 lowercase chars: start with a letter, end with alphanumeric, optionally separated by ".", "_" or "-"'
|
|
4253
5875
|
).openapi("ResourceName");
|
|
5876
|
+
var PAGE_LIMIT = 25;
|
|
5877
|
+
var EVENTS_PAGE_LIMIT = 100;
|
|
4254
5878
|
function uniqueNames(entries, ctx) {
|
|
4255
5879
|
const seen = /* @__PURE__ */ new Set();
|
|
4256
5880
|
for (const entry of entries) {
|
|
@@ -4281,9 +5905,9 @@ var CreateAgentRequestSchema = z2.object({
|
|
|
4281
5905
|
name: NameSchema,
|
|
4282
5906
|
manifest: AgentSpecSchema2
|
|
4283
5907
|
}).strict().openapi("CreateAgentRequest");
|
|
4284
|
-
var
|
|
5908
|
+
var UpdateAgentRequestSchema = z2.object({
|
|
4285
5909
|
manifest: AgentSpecSchema2
|
|
4286
|
-
}).strict().openapi("
|
|
5910
|
+
}).strict().openapi("UpdateAgentRequest");
|
|
4287
5911
|
var AgentSchema = z2.object({
|
|
4288
5912
|
id: z2.string().min(1).describe("Immutable server-generated agent identifier."),
|
|
4289
5913
|
name: NameSchema,
|
|
@@ -4304,15 +5928,26 @@ var RequestErrorResponseSchema = z3.object({
|
|
|
4304
5928
|
})
|
|
4305
5929
|
}).openapi("RequestErrorResponse");
|
|
4306
5930
|
|
|
5931
|
+
// src/routes/openapiTags.ts
|
|
5932
|
+
var OPENAPI_DOCUMENT_TAGS = [
|
|
5933
|
+
{ name: "Auth" /* AUTH */ },
|
|
5934
|
+
{ name: "Capabilities" /* CAPABILITIES */ },
|
|
5935
|
+
{ name: "Models" /* MODELS */ },
|
|
5936
|
+
{ name: "MCP Servers" /* MCP_SERVERS */ },
|
|
5937
|
+
{ name: "Skills" /* SKILLS */ },
|
|
5938
|
+
{ name: "Sandboxes" /* SANDBOXES */ },
|
|
5939
|
+
{ name: "Agents" /* AGENTS */ },
|
|
5940
|
+
{ name: "Agent Sessions" /* AGENT_SESSIONS */ }
|
|
5941
|
+
];
|
|
5942
|
+
|
|
4307
5943
|
// src/routes/agentRoutes.ts
|
|
4308
|
-
var AGENTS_TAG = "Agents";
|
|
4309
5944
|
var AgentIdParamsSchema = z4.object({
|
|
4310
5945
|
agent_id: z4.string().min(1).max(64).describe("Immutable agent identifier.")
|
|
4311
5946
|
});
|
|
4312
5947
|
var listAgentsRoute = createRoute({
|
|
4313
5948
|
method: "get",
|
|
4314
5949
|
path: "/",
|
|
4315
|
-
tags: [
|
|
5950
|
+
tags: ["Agents" /* AGENTS */],
|
|
4316
5951
|
summary: "List agents",
|
|
4317
5952
|
description: "All configured agents for the tenant.",
|
|
4318
5953
|
"x-fern-sdk-group-name": ["agents"],
|
|
@@ -4331,7 +5966,7 @@ var listAgentsRoute = createRoute({
|
|
|
4331
5966
|
var createAgentRoute = createRoute({
|
|
4332
5967
|
method: "post",
|
|
4333
5968
|
path: "/",
|
|
4334
|
-
tags: [
|
|
5969
|
+
tags: ["Agents" /* AGENTS */],
|
|
4335
5970
|
summary: "Create an agent",
|
|
4336
5971
|
description: "Creates an agent and allocates an immutable id. Fails if `name` is already taken. Name cannot be changed later.",
|
|
4337
5972
|
"x-fern-sdk-group-name": ["agents"],
|
|
@@ -4364,7 +5999,7 @@ var createAgentRoute = createRoute({
|
|
|
4364
5999
|
var getAgentRoute = createRoute({
|
|
4365
6000
|
method: "get",
|
|
4366
6001
|
path: "/{agent_id}",
|
|
4367
|
-
tags: [
|
|
6002
|
+
tags: ["Agents" /* AGENTS */],
|
|
4368
6003
|
summary: "Get an agent",
|
|
4369
6004
|
description: "Fetch a configured agent by immutable id.",
|
|
4370
6005
|
"x-fern-sdk-group-name": ["agents"],
|
|
@@ -4386,7 +6021,7 @@ var getAgentRoute = createRoute({
|
|
|
4386
6021
|
var deleteAgentRoute = createRoute({
|
|
4387
6022
|
method: "delete",
|
|
4388
6023
|
path: "/{agent_id}",
|
|
4389
|
-
tags: [
|
|
6024
|
+
tags: ["Agents" /* AGENTS */],
|
|
4390
6025
|
summary: "Delete an agent",
|
|
4391
6026
|
description: "Delete a configured agent by immutable id. Idempotent if already gone.",
|
|
4392
6027
|
"x-fern-sdk-group-name": ["agents"],
|
|
@@ -4408,7 +6043,7 @@ var deleteAgentRoute = createRoute({
|
|
|
4408
6043
|
var putAgentRoute = createRoute({
|
|
4409
6044
|
method: "put",
|
|
4410
6045
|
path: "/{agent_id}",
|
|
4411
|
-
tags: [
|
|
6046
|
+
tags: ["Agents" /* AGENTS */],
|
|
4412
6047
|
summary: "Update an agent",
|
|
4413
6048
|
description: "Replaces the manifest for an existing agent keyed by immutable `agent_id`.",
|
|
4414
6049
|
"x-fern-sdk-group-name": ["agents"],
|
|
@@ -4416,7 +6051,7 @@ var putAgentRoute = createRoute({
|
|
|
4416
6051
|
request: {
|
|
4417
6052
|
params: AgentIdParamsSchema,
|
|
4418
6053
|
body: {
|
|
4419
|
-
content: { "application/json": { schema:
|
|
6054
|
+
content: { "application/json": { schema: UpdateAgentRequestSchema } },
|
|
4420
6055
|
required: true
|
|
4421
6056
|
}
|
|
4422
6057
|
},
|
|
@@ -4447,6 +6082,7 @@ import {
|
|
|
4447
6082
|
SkillMounter
|
|
4448
6083
|
} from "@truefoundry/trueforge-core/core";
|
|
4449
6084
|
import { HTTPException } from "hono/http-exception";
|
|
6085
|
+
import { join as join4 } from "path";
|
|
4450
6086
|
|
|
4451
6087
|
// src/mcp/auth/mcpDcr.ts
|
|
4452
6088
|
import {
|
|
@@ -4511,19 +6147,6 @@ function isOAuthAccessTokenUsable(expiresAtIso, nowMs) {
|
|
|
4511
6147
|
const expiresAtMs = Date.parse(expiresAtIso);
|
|
4512
6148
|
return !Number.isNaN(expiresAtMs) && expiresAtMs > nowMs;
|
|
4513
6149
|
}
|
|
4514
|
-
function validateRedirectUris({ redirectUris }) {
|
|
4515
|
-
for (const redirectUri of redirectUris) {
|
|
4516
|
-
let parsed;
|
|
4517
|
-
try {
|
|
4518
|
-
parsed = new URL(redirectUri);
|
|
4519
|
-
} catch (error) {
|
|
4520
|
-
throw new McpConnectionError(`Invalid redirect URI: ${redirectUri}. Must be a valid URL.`, 400, { cause: error });
|
|
4521
|
-
}
|
|
4522
|
-
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
4523
|
-
throw new McpConnectionError(`Invalid redirect URI: ${redirectUri}. Must be a valid URL.`, 400);
|
|
4524
|
-
}
|
|
4525
|
-
}
|
|
4526
|
-
}
|
|
4527
6150
|
|
|
4528
6151
|
// src/mcp/auth/mcpDcr.ts
|
|
4529
6152
|
var MCP_OAUTH_HTTP_TIMEOUT_MS = 15e3;
|
|
@@ -4674,7 +6297,7 @@ async function buildMcpAuthorizationUrl(params) {
|
|
|
4674
6297
|
userRef: params.userRef,
|
|
4675
6298
|
mcpServerUrl: params.mcpServerUrl,
|
|
4676
6299
|
codeVerifier: started.codeVerifier,
|
|
4677
|
-
|
|
6300
|
+
returnTo: params.returnTo ?? null
|
|
4678
6301
|
});
|
|
4679
6302
|
return started.authorizationUrl;
|
|
4680
6303
|
}
|
|
@@ -4717,7 +6340,7 @@ async function resolveMcpAuth(params) {
|
|
|
4717
6340
|
userRef: params.userRef,
|
|
4718
6341
|
mcpServerUrl: params.mcpServerUrl,
|
|
4719
6342
|
mcpServerName: params.mcpServerName,
|
|
4720
|
-
...params.
|
|
6343
|
+
...params.returnTo !== void 0 ? { returnTo: params.returnTo } : {}
|
|
4721
6344
|
});
|
|
4722
6345
|
return { authUrl };
|
|
4723
6346
|
}
|
|
@@ -4761,46 +6384,49 @@ async function completeMcpAuthorization(params) {
|
|
|
4761
6384
|
});
|
|
4762
6385
|
}
|
|
4763
6386
|
|
|
6387
|
+
// src/runtime/sessionResources.ts
|
|
6388
|
+
init_LocalSandboxProvider();
|
|
6389
|
+
|
|
4764
6390
|
// src/sandbox/providerUtils.ts
|
|
4765
6391
|
init_config();
|
|
4766
6392
|
import { Daytona, DaytonaError } from "@daytona/sdk";
|
|
4767
6393
|
import { DaytonaSandboxProvider, SANDBOX_IMAGE_URI } from "@truefoundry/trueforge-core/core";
|
|
4768
6394
|
|
|
4769
6395
|
// src/schemas/sandboxProvider.ts
|
|
4770
|
-
import { z as
|
|
4771
|
-
var DaytonaSandboxProviderAuthSchema =
|
|
4772
|
-
api_key:
|
|
6396
|
+
import { z as z7 } from "@hono/zod-openapi";
|
|
6397
|
+
var DaytonaSandboxProviderAuthSchema = z7.object({
|
|
6398
|
+
api_key: z7.string().min(1).describe(
|
|
4773
6399
|
"Daytona API key. Responses are redacted; on PUT, a real value sets/rotates and a redacted value keeps the stored key."
|
|
4774
6400
|
)
|
|
4775
6401
|
}).strict().describe("Daytona authentication credentials.").openapi("DaytonaSandboxProviderAuth");
|
|
4776
|
-
var DaytonaSandboxProviderSchema =
|
|
4777
|
-
type:
|
|
6402
|
+
var DaytonaSandboxProviderSchema = z7.object({
|
|
6403
|
+
type: z7.literal("daytona").describe("Daytona sandbox provider."),
|
|
4778
6404
|
auth: DaytonaSandboxProviderAuthSchema,
|
|
4779
|
-
exec_timeout_ms:
|
|
4780
|
-
auto_stop_interval_in_minutes:
|
|
4781
|
-
auto_archive_interval_in_minutes:
|
|
4782
|
-
auto_delete_interval_in_minutes:
|
|
6405
|
+
exec_timeout_ms: z7.number().int().positive().describe("Default sandbox command exec timeout in milliseconds."),
|
|
6406
|
+
auto_stop_interval_in_minutes: z7.number().int().nonnegative().describe("Minutes of idle time before Daytona auto-stops the sandbox (0 disables)."),
|
|
6407
|
+
auto_archive_interval_in_minutes: z7.number().int().nonnegative().describe("Minutes before Daytona auto-archives the sandbox (0 disables)."),
|
|
6408
|
+
auto_delete_interval_in_minutes: z7.number().int().nonnegative().describe("Minutes before Daytona auto-deletes the sandbox (0 disables).")
|
|
4783
6409
|
}).strict();
|
|
4784
6410
|
var SandboxProviderManifestSchema = DaytonaSandboxProviderSchema.openapi("SandboxProviderManifest");
|
|
4785
|
-
var SandboxBuildStatusSchema =
|
|
4786
|
-
var SandboxBuildMetadataSchema =
|
|
4787
|
-
var SandboxStatusSchema =
|
|
6411
|
+
var SandboxBuildStatusSchema = z7.enum(["pending", "ready", "failed"]).describe("Current build status.").openapi("SandboxBuildStatus");
|
|
6412
|
+
var SandboxBuildMetadataSchema = z7.record(z7.string(), z7.string()).describe("Provider-specific build metadata (opaque string map).");
|
|
6413
|
+
var SandboxStatusSchema = z7.object({
|
|
4788
6414
|
status: SandboxBuildStatusSchema,
|
|
4789
|
-
status_reason:
|
|
6415
|
+
status_reason: z7.string().nullable().describe("Human-readable detail for the current status; null when ready."),
|
|
4790
6416
|
build_metadata: SandboxBuildMetadataSchema.nullable().describe(
|
|
4791
6417
|
"Provider-specific build metadata; null when the provider has none."
|
|
4792
6418
|
)
|
|
4793
6419
|
}).strict();
|
|
4794
|
-
var
|
|
6420
|
+
var ConfiguredSandboxProviderSchema = z7.object({
|
|
4795
6421
|
manifest: SandboxProviderManifestSchema,
|
|
4796
6422
|
status: SandboxBuildStatusSchema,
|
|
4797
|
-
status_reason:
|
|
4798
|
-
}).strict().openapi("
|
|
4799
|
-
var
|
|
6423
|
+
status_reason: z7.string().nullable().describe("Human-readable detail for the current status; null when ready.")
|
|
6424
|
+
}).strict().openapi("ConfiguredSandboxProvider");
|
|
6425
|
+
var UpdateSandboxProviderRequestSchema = z7.object({
|
|
4800
6426
|
manifest: SandboxProviderManifestSchema
|
|
4801
|
-
}).strict().openapi("
|
|
4802
|
-
var GetSandboxProviderResponseSchema =
|
|
4803
|
-
data:
|
|
6427
|
+
}).strict().openapi("UpdateSandboxProviderRequest");
|
|
6428
|
+
var GetSandboxProviderResponseSchema = z7.object({
|
|
6429
|
+
data: ConfiguredSandboxProviderSchema
|
|
4804
6430
|
}).openapi("GetSandboxProviderResponse");
|
|
4805
6431
|
function toDaytonaSandboxProviderInput(manifest) {
|
|
4806
6432
|
return {
|
|
@@ -4881,57 +6507,57 @@ async function checkSnapshotStatus({
|
|
|
4881
6507
|
}
|
|
4882
6508
|
|
|
4883
6509
|
// src/schemas/mcpServer.ts
|
|
4884
|
-
import { z as
|
|
4885
|
-
var McpServerTypeSchema =
|
|
4886
|
-
var McpServerHeaderAuthSchema =
|
|
4887
|
-
type:
|
|
4888
|
-
headers:
|
|
6510
|
+
import { z as z8 } from "@hono/zod-openapi";
|
|
6511
|
+
var McpServerTypeSchema = z8.enum(["remote"]).openapi("MCPServerType");
|
|
6512
|
+
var McpServerHeaderAuthSchema = z8.object({
|
|
6513
|
+
type: z8.literal("header").describe("Authenticate with static HTTP headers."),
|
|
6514
|
+
headers: z8.record(z8.string().min(1), z8.string().min(1)).refine((headers) => Object.keys(headers).length > 0, {
|
|
4889
6515
|
message: "must include at least one header"
|
|
4890
6516
|
}).describe(
|
|
4891
6517
|
"Request headers for this MCP server. Responses are redacted; on PUT, a real value sets/rotates and a redacted value keeps the stored secret for that header name."
|
|
4892
6518
|
)
|
|
4893
|
-
}).strict().openapi("
|
|
4894
|
-
var McpServerDcrAuthSchema =
|
|
4895
|
-
type:
|
|
4896
|
-
}).strict().openapi("
|
|
4897
|
-
var McpServerManifestAuthSchema =
|
|
4898
|
-
var McpServerDescriptionSchema =
|
|
4899
|
-
var McpServerManifestObjectSchema =
|
|
6519
|
+
}).strict().openapi("MCPServerHeaderAuth");
|
|
6520
|
+
var McpServerDcrAuthSchema = z8.object({
|
|
6521
|
+
type: z8.literal("dcr").describe("Authenticate via OAuth Dynamic Client Registration.")
|
|
6522
|
+
}).strict().openapi("MCPServerDcrAuth");
|
|
6523
|
+
var McpServerManifestAuthSchema = z8.discriminatedUnion("type", [McpServerHeaderAuthSchema, McpServerDcrAuthSchema]).describe("Optional auth settings. Omit when the server needs no credentials.").openapi("MCPServerManifestAuth");
|
|
6524
|
+
var McpServerDescriptionSchema = z8.string().trim().min(1).describe("Concise summary of what this MCP server provides.");
|
|
6525
|
+
var McpServerManifestObjectSchema = z8.object({
|
|
4900
6526
|
type: McpServerTypeSchema,
|
|
4901
6527
|
name: NameSchema,
|
|
4902
|
-
url:
|
|
6528
|
+
url: z8.url().describe("URL of the remote MCP server."),
|
|
4903
6529
|
description: McpServerDescriptionSchema,
|
|
4904
6530
|
auth: McpServerManifestAuthSchema.optional()
|
|
4905
6531
|
}).strict();
|
|
4906
|
-
var McpServerManifestSchema = McpServerManifestObjectSchema.openapi("
|
|
4907
|
-
var McpAuthStatusSchema =
|
|
4908
|
-
status:
|
|
4909
|
-
authorization_url:
|
|
4910
|
-
}).strict().describe("Current auth state.").openapi("
|
|
4911
|
-
var ConfiguredMcpServerSchema =
|
|
6532
|
+
var McpServerManifestSchema = McpServerManifestObjectSchema.openapi("MCPServerManifest");
|
|
6533
|
+
var McpAuthStatusSchema = z8.object({
|
|
6534
|
+
status: z8.enum(["authenticated", "auth_required", "not_required"]).describe("Current auth state for this MCP server."),
|
|
6535
|
+
authorization_url: z8.url().optional().describe("When auth is required, this contains the URL to redirect the user to for authorization.")
|
|
6536
|
+
}).strict().describe("Current auth state.").openapi("MCPAuthStatus");
|
|
6537
|
+
var ConfiguredMcpServerSchema = z8.object({
|
|
4912
6538
|
name: NameSchema,
|
|
4913
6539
|
manifest: McpServerManifestSchema,
|
|
4914
6540
|
auth_status: McpAuthStatusSchema
|
|
4915
|
-
}).strict().openapi("
|
|
4916
|
-
var CreateMcpServerRequestSchema =
|
|
6541
|
+
}).strict().openapi("ConfiguredMCPServer");
|
|
6542
|
+
var CreateMcpServerRequestSchema = z8.object({
|
|
4917
6543
|
manifest: McpServerManifestSchema
|
|
4918
|
-
}).strict().openapi("
|
|
4919
|
-
var
|
|
6544
|
+
}).strict().openapi("CreateMCPServerRequest");
|
|
6545
|
+
var UpdateMcpServerRequestSchema = z8.object({
|
|
4920
6546
|
manifest: McpServerManifestSchema
|
|
4921
|
-
}).strict().openapi("
|
|
4922
|
-
var GetMcpServerResponseSchema =
|
|
4923
|
-
var ListMcpServersResponseSchema =
|
|
4924
|
-
var McpServerAuthPublicSchema =
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
]).describe("Auth mechanism when configured (no secrets). Omit when the server needs no credentials.").openapi("
|
|
4928
|
-
var
|
|
6547
|
+
}).strict().openapi("UpdateMCPServerRequest");
|
|
6548
|
+
var GetMcpServerResponseSchema = z8.object({ data: ConfiguredMcpServerSchema }).openapi("GetMCPServerResponse");
|
|
6549
|
+
var ListMcpServersResponseSchema = z8.object({ data: z8.array(ConfiguredMcpServerSchema) }).openapi("ListMCPServersResponse");
|
|
6550
|
+
var McpServerAuthPublicSchema = z8.discriminatedUnion("type", [
|
|
6551
|
+
z8.object({ type: z8.literal("dcr") }).strict(),
|
|
6552
|
+
z8.object({ type: z8.literal("header") }).strict()
|
|
6553
|
+
]).describe("Auth mechanism when configured (no secrets). Omit when the server needs no credentials.").openapi("MCPServerAuthPublic");
|
|
6554
|
+
var AvailableMcpServerSchema = z8.object({
|
|
4929
6555
|
name: NameSchema,
|
|
4930
|
-
url:
|
|
6556
|
+
url: z8.url().describe("URL of the remote MCP server."),
|
|
4931
6557
|
auth: McpServerAuthPublicSchema.optional(),
|
|
4932
6558
|
auth_status: McpAuthStatusSchema
|
|
4933
|
-
}).strict().openapi("
|
|
4934
|
-
var ListAvailableMcpServersResponseSchema =
|
|
6559
|
+
}).strict().openapi("AvailableMCPServer");
|
|
6560
|
+
var ListAvailableMcpServersResponseSchema = z8.object({ data: z8.array(AvailableMcpServerSchema) }).openapi("ListAvailableMCPServersResponse");
|
|
4935
6561
|
function resolveConfiguredMcpRequestHeaders(manifest) {
|
|
4936
6562
|
if (manifest.auth?.type === "header") {
|
|
4937
6563
|
return { ...manifest.auth.headers };
|
|
@@ -5079,20 +6705,40 @@ async function resolveGitSkills({
|
|
|
5079
6705
|
}
|
|
5080
6706
|
return resolved;
|
|
5081
6707
|
}
|
|
6708
|
+
function localSandboxSessionSegment(sessionId) {
|
|
6709
|
+
if (sessionId === void 0 || sessionId.length === 0 || sessionId.includes("/") || sessionId.includes("..")) {
|
|
6710
|
+
return "_";
|
|
6711
|
+
}
|
|
6712
|
+
return sessionId;
|
|
6713
|
+
}
|
|
5082
6714
|
async function resolveSandboxProvider({
|
|
5083
6715
|
tenant_id,
|
|
5084
6716
|
store,
|
|
5085
|
-
logger
|
|
6717
|
+
logger,
|
|
6718
|
+
sessionId
|
|
5086
6719
|
}) {
|
|
5087
6720
|
const record = await store.getSandboxProvider(tenant_id);
|
|
5088
|
-
if (record
|
|
6721
|
+
if (record !== void 0) {
|
|
6722
|
+
return toDaytonaSandboxProvider({
|
|
6723
|
+
manifest: record.manifest,
|
|
6724
|
+
tenant_id,
|
|
6725
|
+
logger,
|
|
6726
|
+
build_metadata: record.build_metadata
|
|
6727
|
+
});
|
|
6728
|
+
}
|
|
6729
|
+
if (!config_default.STANDALONE) {
|
|
5089
6730
|
return void 0;
|
|
5090
6731
|
}
|
|
5091
|
-
|
|
5092
|
-
|
|
5093
|
-
|
|
5094
|
-
|
|
5095
|
-
|
|
6732
|
+
const support = getCachedLocalSandboxSupport();
|
|
6733
|
+
if (support?.supported !== true) {
|
|
6734
|
+
return void 0;
|
|
6735
|
+
}
|
|
6736
|
+
return new LocalSandboxProvider({
|
|
6737
|
+
sandboxRootPathParent: join4(config_default.LOCAL_SANDBOX_ROOT_PARENT, localSandboxSessionSegment(sessionId)),
|
|
6738
|
+
codeModeSocketParentPath: config_default.CODE_MODE_SOCKET_PARENT,
|
|
6739
|
+
support,
|
|
6740
|
+
fileMaxBytesForDownload: config_default.SANDBOX_FILE_MAX_BYTES_FOR_DOWNLOAD,
|
|
6741
|
+
logger
|
|
5096
6742
|
});
|
|
5097
6743
|
}
|
|
5098
6744
|
function buildTurnSandbox(input) {
|
|
@@ -5104,9 +6750,6 @@ function buildTurnSandbox(input) {
|
|
|
5104
6750
|
blockDestructiveToolsInCodeMode: true,
|
|
5105
6751
|
mcpRequestTimeoutMs: config_default.MCP_REQUEST_TIMEOUT_MS,
|
|
5106
6752
|
mcpConnectTimeoutMs: config_default.MCP_CONNECT_TIMEOUT_MS,
|
|
5107
|
-
// Sandbox reads its tenant from TFY_TENANT_NAME for the ownership check
|
|
5108
|
-
// against provider-created sandbox ids (`<tenant>.<uuid>`).
|
|
5109
|
-
execExtraEnv: { TFY_TENANT_NAME: input.tenantName },
|
|
5110
6753
|
...skillMounter ? { skillMounter } : {},
|
|
5111
6754
|
tracing: input.tracing,
|
|
5112
6755
|
logger: input.logger
|
|
@@ -5175,7 +6818,7 @@ async function validateAgentSpec({
|
|
|
5175
6818
|
const hasSkills = requestedSkills.length > 0;
|
|
5176
6819
|
if (wantsSandbox || hasSkills) {
|
|
5177
6820
|
const record = await sandboxProviderStore.getSandboxProvider(tenant_id);
|
|
5178
|
-
if (record === void 0) {
|
|
6821
|
+
if (record === void 0 && !isLocalSandboxFallbackEnabled()) {
|
|
5179
6822
|
throw new HTTPException(422, {
|
|
5180
6823
|
message: hasSkills ? "skills require a sandbox provider \u2014 configure via PUT /settings/sandbox-providers" : "sandbox is enabled but no sandbox provider is configured \u2014 PUT /settings/sandbox-providers"
|
|
5181
6824
|
});
|
|
@@ -5198,14 +6841,14 @@ import {
|
|
|
5198
6841
|
redisRequest,
|
|
5199
6842
|
RequestTimeoutError
|
|
5200
6843
|
} from "@truefoundry/trueforge-core/request-reply";
|
|
5201
|
-
import { ulid as
|
|
5202
|
-
import { z as
|
|
6844
|
+
import { ulid as ulid4 } from "ulid";
|
|
6845
|
+
import { z as z13 } from "zod";
|
|
5203
6846
|
|
|
5204
6847
|
// src/routes/sessionRoutes.ts
|
|
5205
|
-
import { createRoute as createRoute2, z as
|
|
6848
|
+
import { createRoute as createRoute2, z as z12 } from "@hono/zod-openapi";
|
|
5206
6849
|
|
|
5207
6850
|
// src/schemas/events.ts
|
|
5208
|
-
import { z as
|
|
6851
|
+
import { z as z9 } from "@hono/zod-openapi";
|
|
5209
6852
|
import {
|
|
5210
6853
|
EventType,
|
|
5211
6854
|
SessionEventItemSchema,
|
|
@@ -5225,7 +6868,7 @@ import {
|
|
|
5225
6868
|
ToolResponseEventSchema,
|
|
5226
6869
|
ToolResponseRequiredEventSchema
|
|
5227
6870
|
} from "@truefoundry/trueforge-core/core";
|
|
5228
|
-
var TurnStreamingEventSchema =
|
|
6871
|
+
var TurnStreamingEventSchema = z9.discriminatedUnion("type", [
|
|
5229
6872
|
ModelMessageEventSchema,
|
|
5230
6873
|
ModelMessageDeltaEventSchema,
|
|
5231
6874
|
ToolResponseEventSchema,
|
|
@@ -5239,105 +6882,94 @@ var TurnStreamingEventSchema = z7.discriminatedUnion("type", [
|
|
|
5239
6882
|
TurnCreatedEventSchema,
|
|
5240
6883
|
TurnDoneEventSchema
|
|
5241
6884
|
]).openapi("TurnStreamingEvent");
|
|
5242
|
-
var
|
|
5243
|
-
|
|
5244
|
-
page_token: z7.string().optional().describe(
|
|
6885
|
+
var ListSessionEventsRequestQuerySchema = z9.object({
|
|
6886
|
+
page_token: z9.string().optional().describe(
|
|
5245
6887
|
"Pagination cursor from `pagination.next_page_token`. It retains the branch anchor turn and returns older events toward the session start."
|
|
5246
6888
|
),
|
|
5247
|
-
last_turn_id:
|
|
6889
|
+
last_turn_id: z9.string().optional().describe(
|
|
5248
6890
|
"Newest turn in the listing window (initial load only; ignored when `page_token` is set). Lists that turn and its ancestors, newest events first. Omit to use the session last turn."
|
|
5249
6891
|
),
|
|
5250
|
-
limit:
|
|
5251
|
-
`Max events per response. Default ${String(SESSION_EVENTS_LIMIT)}, max ${String(SESSION_EVENTS_LIMIT)}.`
|
|
5252
|
-
)
|
|
6892
|
+
limit: z9.coerce.number().int().min(1).max(EVENTS_PAGE_LIMIT).optional().default(EVENTS_PAGE_LIMIT).describe(`Page size. Defaults to ${String(EVENTS_PAGE_LIMIT)}, max ${String(EVENTS_PAGE_LIMIT)}.`)
|
|
5253
6893
|
}).openapi("ListSessionEventsRequestQuery");
|
|
5254
|
-
var ListSessionEventsResponseSchema =
|
|
5255
|
-
data:
|
|
6894
|
+
var ListSessionEventsResponseSchema = z9.object({
|
|
6895
|
+
data: z9.array(SessionEventItemSchema),
|
|
5256
6896
|
pagination: TokenPaginationSchema
|
|
5257
6897
|
}).openapi("ListSessionEventsResponse");
|
|
5258
6898
|
|
|
5259
6899
|
// src/schemas/session.ts
|
|
5260
|
-
import { z as
|
|
6900
|
+
import { z as z10 } from "@hono/zod-openapi";
|
|
5261
6901
|
import { AgentSpecSchema as AgentSpecSchema3, SessionSchema, TokenPaginationSchema as TokenPaginationSchema2 } from "@truefoundry/trueforge-core/agent-session";
|
|
5262
|
-
var SessionAgentNameRefSchema =
|
|
5263
|
-
var SessionAgentSpecBodySchema =
|
|
5264
|
-
var CreateSessionAgentSchema =
|
|
6902
|
+
var SessionAgentNameRefSchema = z10.object({ name: NameSchema }).strict().openapi("SessionAgentNameRef");
|
|
6903
|
+
var SessionAgentSpecBodySchema = z10.object({ spec: AgentSpecSchema3 }).strict().openapi("SessionAgentSpecBody");
|
|
6904
|
+
var CreateSessionAgentSchema = z10.union([SessionAgentNameRefSchema, SessionAgentSpecBodySchema]).openapi("CreateSessionAgent");
|
|
5265
6905
|
function isSessionAgentNameRef(agent) {
|
|
5266
6906
|
return SessionAgentNameRefSchema.safeParse(agent).success;
|
|
5267
6907
|
}
|
|
5268
|
-
var CreateSessionRequestSchema =
|
|
6908
|
+
var CreateSessionRequestSchema = z10.object({
|
|
5269
6909
|
agent: CreateSessionAgentSchema
|
|
5270
6910
|
}).strict().openapi("CreateSessionRequest");
|
|
5271
|
-
var UpdateSessionRequestSchema =
|
|
6911
|
+
var UpdateSessionRequestSchema = z10.object({
|
|
5272
6912
|
agent: SessionAgentSpecBodySchema.optional()
|
|
5273
6913
|
}).strict().openapi("UpdateSessionRequest");
|
|
5274
|
-
var
|
|
5275
|
-
var
|
|
5276
|
-
|
|
5277
|
-
|
|
5278
|
-
|
|
5279
|
-
order: z8.enum(["asc", "desc"]).optional().default("desc").describe('Sort sessions by `updated_at`. Defaults to "desc".').openapi("ListSessionsOrder"),
|
|
5280
|
-
page_token: z8.string().min(1).optional().describe("Opaque keyset cursor from a previous response `next_page_token`."),
|
|
6914
|
+
var IsoTimestampQueryParam = z10.iso.datetime({ offset: true }).openapi({ type: "string", format: "date-time" }).transform((s) => new Date(s));
|
|
6915
|
+
var ListSessionsRequestQuerySchema = z10.object({
|
|
6916
|
+
limit: z10.coerce.number().int().min(1).max(PAGE_LIMIT).optional().default(PAGE_LIMIT).describe(`Page size. Defaults to ${String(PAGE_LIMIT)}, max ${String(PAGE_LIMIT)}.`),
|
|
6917
|
+
order: z10.enum(["asc", "desc"]).optional().default("desc").describe('Sort sessions by `updated_at`. Defaults to "desc".').openapi("ListSessionsOrder"),
|
|
6918
|
+
page_token: z10.string().min(1).optional().describe("Opaque keyset cursor from a previous response `next_page_token`."),
|
|
5281
6919
|
start_timestamp: IsoTimestampQueryParam.optional().describe(
|
|
5282
6920
|
"Inclusive lower bound on `created_at` (ISO-8601 / RFC 3339)."
|
|
5283
6921
|
),
|
|
5284
6922
|
end_timestamp: IsoTimestampQueryParam.optional().describe(
|
|
5285
6923
|
"Inclusive upper bound on `created_at` (ISO-8601 / RFC 3339)."
|
|
5286
6924
|
),
|
|
5287
|
-
agent_id:
|
|
6925
|
+
agent_id: z10.string().min(1).optional().describe("When set, only sessions bound to this agent id are returned.")
|
|
5288
6926
|
}).openapi("ListSessionsRequestQuery");
|
|
5289
|
-
var GetSessionResponseSchema =
|
|
6927
|
+
var GetSessionResponseSchema = z10.object({
|
|
5290
6928
|
data: SessionSchema
|
|
5291
6929
|
}).openapi("GetSessionResponse");
|
|
5292
|
-
var ListSessionsResponseSchema =
|
|
5293
|
-
data:
|
|
6930
|
+
var ListSessionsResponseSchema = z10.object({
|
|
6931
|
+
data: z10.array(SessionSchema),
|
|
5294
6932
|
pagination: TokenPaginationSchema2
|
|
5295
6933
|
}).openapi("ListSessionsResponse");
|
|
5296
6934
|
|
|
5297
6935
|
// src/schemas/turn.ts
|
|
5298
|
-
import { z as
|
|
6936
|
+
import { z as z11 } from "@hono/zod-openapi";
|
|
5299
6937
|
import { SessionEventSchema, TokenPaginationSchema as TokenPaginationSchema3, TurnSchema } from "@truefoundry/trueforge-core/agent-session";
|
|
5300
6938
|
import { CreateTurnRequestSchema, TurnSchema as TurnSchema2 } from "@truefoundry/trueforge-core/agent-session";
|
|
5301
|
-
var SubscribeTurnQuerySchema =
|
|
6939
|
+
var SubscribeTurnQuerySchema = z11.object({
|
|
5302
6940
|
// Query strings need coerce; map null/'' to undefined first so Number(null)→0
|
|
5303
6941
|
// cannot silently become a resume cursor (and so OpenAPI does not advertise null).
|
|
5304
|
-
after_sequence_number:
|
|
6942
|
+
after_sequence_number: z11.preprocess(
|
|
5305
6943
|
(val) => val === null || val === "" ? void 0 : val,
|
|
5306
|
-
|
|
6944
|
+
z11.coerce.number().int().nonnegative().optional().describe(
|
|
5307
6945
|
"Exclusive resume cursor: replay only events with a sequence number greater than this value. Omit to start from the beginning of the live buffer."
|
|
5308
6946
|
)
|
|
5309
6947
|
)
|
|
5310
6948
|
}).openapi("SubscribeTurnQuery");
|
|
5311
|
-
var CancelSessionRequestSchema =
|
|
5312
|
-
var CancelSessionResponseSchema =
|
|
5313
|
-
var
|
|
5314
|
-
|
|
5315
|
-
|
|
5316
|
-
limit: z9.coerce.number().int().min(1).max(TURNS_MAX_LIMIT).optional().default(DEFAULT_TURNS_LIMIT).describe(`Page size. Defaults to ${String(DEFAULT_TURNS_LIMIT)}, max ${String(TURNS_MAX_LIMIT)}.`),
|
|
5317
|
-
page_token: z9.string().optional().describe("Opaque token from a previous response `next_page_token`.")
|
|
6949
|
+
var CancelSessionRequestSchema = z11.object({}).describe("Empty request body. Cancel identifies the session via the path only.").openapi("CancelSessionRequest");
|
|
6950
|
+
var CancelSessionResponseSchema = z11.object({}).describe("Empty success body. HTTP 200 means the cancel request was accepted (or nothing was running).").openapi("CancelSessionResponse");
|
|
6951
|
+
var ListTurnsRequestQuerySchema = z11.object({
|
|
6952
|
+
limit: z11.coerce.number().int().min(1).max(PAGE_LIMIT).optional().default(PAGE_LIMIT).describe(`Page size. Defaults to ${String(PAGE_LIMIT)}, max ${String(PAGE_LIMIT)}.`),
|
|
6953
|
+
page_token: z11.string().optional().describe("Opaque token from a previous response `next_page_token`.")
|
|
5318
6954
|
}).openapi("ListTurnsRequestQuery");
|
|
5319
|
-
var GetTurnResponseSchema =
|
|
6955
|
+
var GetTurnResponseSchema = z11.object({
|
|
5320
6956
|
data: TurnSchema
|
|
5321
6957
|
}).openapi("GetTurnResponse");
|
|
5322
|
-
var ListTurnsResponseSchema =
|
|
5323
|
-
data:
|
|
6958
|
+
var ListTurnsResponseSchema = z11.object({
|
|
6959
|
+
data: z11.array(TurnSchema),
|
|
5324
6960
|
pagination: TokenPaginationSchema3
|
|
5325
6961
|
}).openapi("ListTurnsResponse");
|
|
5326
|
-
var
|
|
5327
|
-
|
|
5328
|
-
|
|
5329
|
-
|
|
5330
|
-
`Max events per response. Default ${String(DEFAULT_TURN_EVENTS_LIMIT)}, max ${String(TURN_EVENTS_MAX_LIMIT)}.`
|
|
5331
|
-
),
|
|
5332
|
-
page_token: z9.string().optional().describe("Opaque token from a previous response `next_page_token`."),
|
|
5333
|
-
order: z9.enum(["asc", "desc"]).optional().default("asc").describe('Sort events by insertion order. Defaults to "asc".').openapi("ListTurnEventsOrder")
|
|
6962
|
+
var ListTurnEventsRequestQuerySchema = z11.object({
|
|
6963
|
+
limit: z11.coerce.number().int().min(1).max(EVENTS_PAGE_LIMIT).optional().default(EVENTS_PAGE_LIMIT).describe(`Page size. Defaults to ${String(EVENTS_PAGE_LIMIT)}, max ${String(EVENTS_PAGE_LIMIT)}.`),
|
|
6964
|
+
page_token: z11.string().optional().describe("Opaque token from a previous response `next_page_token`."),
|
|
6965
|
+
order: z11.enum(["asc", "desc"]).optional().default("asc").describe('Sort events by insertion order. Defaults to "asc".').openapi("ListTurnEventsOrder")
|
|
5334
6966
|
}).openapi("ListTurnEventsRequestQuery");
|
|
5335
|
-
var ListTurnEventsResponseSchema =
|
|
5336
|
-
data:
|
|
6967
|
+
var ListTurnEventsResponseSchema = z11.object({
|
|
6968
|
+
data: z11.array(SessionEventSchema),
|
|
5337
6969
|
pagination: TokenPaginationSchema3
|
|
5338
6970
|
}).openapi("ListTurnEventsResponse");
|
|
5339
|
-
var DownloadSandboxFileRequestQuerySchema =
|
|
5340
|
-
path:
|
|
6971
|
+
var DownloadSandboxFileRequestQuerySchema = z11.object({
|
|
6972
|
+
path: z11.string().min(1).describe(
|
|
5341
6973
|
"Absolute path of the file inside the sandbox, as listed in the assistant's `sandbox_artifacts` block."
|
|
5342
6974
|
)
|
|
5343
6975
|
}).openapi("DownloadSandboxFileRequestQuery");
|
|
@@ -5350,14 +6982,13 @@ var TOKEN_PAGINATION = {
|
|
|
5350
6982
|
};
|
|
5351
6983
|
|
|
5352
6984
|
// src/routes/sessionRoutes.ts
|
|
5353
|
-
var
|
|
5354
|
-
|
|
5355
|
-
session_id: z10.string().min(1).max(64).describe("Session identifier.")
|
|
6985
|
+
var SessionIdParamsSchema = z12.object({
|
|
6986
|
+
session_id: z12.string().min(1).max(64).describe("Session identifier.")
|
|
5356
6987
|
});
|
|
5357
6988
|
var createSessionRoute = createRoute2({
|
|
5358
6989
|
method: "post",
|
|
5359
6990
|
path: "/",
|
|
5360
|
-
tags: [
|
|
6991
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5361
6992
|
summary: "Create a session",
|
|
5362
6993
|
description: 'Create a session with `agent` as either `{ name }` (named registry binding) or `{ spec: AgentSpec }` (inline). Named sessions snapshot the agent name at create and resolve the live agent on each turn. Responses use `{ type: "reference", name, id }` or `{ type: "inline", spec }`.',
|
|
5363
6994
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5390,7 +7021,7 @@ var createSessionRoute = createRoute2({
|
|
|
5390
7021
|
var getSessionRoute = createRoute2({
|
|
5391
7022
|
method: "get",
|
|
5392
7023
|
path: "/{session_id}",
|
|
5393
|
-
tags: [
|
|
7024
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5394
7025
|
summary: "Get a session",
|
|
5395
7026
|
description: "Fetch a session by ID. Only the session creator (`created_by`) may fetch it.",
|
|
5396
7027
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5416,7 +7047,7 @@ var getSessionRoute = createRoute2({
|
|
|
5416
7047
|
var deleteSessionRoute = createRoute2({
|
|
5417
7048
|
method: "delete",
|
|
5418
7049
|
path: "/{session_id}",
|
|
5419
|
-
tags: [
|
|
7050
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5420
7051
|
summary: "Delete a session",
|
|
5421
7052
|
description: "Delete a session and all related turns, events, and internal state. Only the session creator (`created_by`) may delete it. Idempotent if already gone.",
|
|
5422
7053
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5437,7 +7068,7 @@ var deleteSessionRoute = createRoute2({
|
|
|
5437
7068
|
var updateSessionRoute = createRoute2({
|
|
5438
7069
|
method: "patch",
|
|
5439
7070
|
path: "/{session_id}",
|
|
5440
|
-
tags: [
|
|
7071
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5441
7072
|
summary: "Update a session",
|
|
5442
7073
|
description: "Update a session by replacing `agent` with `{ spec: AgentSpec }`. Named (reference) sessions reject agent updates. An empty body is a valid no-op that refreshes `updated_at`. Only the session creator (`created_by`) may update it.",
|
|
5443
7074
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5475,7 +7106,7 @@ var updateSessionRoute = createRoute2({
|
|
|
5475
7106
|
var listSessionsRoute = createRoute2({
|
|
5476
7107
|
method: "get",
|
|
5477
7108
|
path: "/",
|
|
5478
|
-
tags: [
|
|
7109
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5479
7110
|
summary: "List sessions",
|
|
5480
7111
|
description: "List the caller's sessions (newest first by default), token-paginated. Results are scoped to the authenticated identity via the session store's `created_by` filter (not a client query param). Optional `agent_id` filters to sessions bound to that named agent. Pass `page_token` to fetch the next page, keeping the other query params constant.",
|
|
5481
7112
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5498,7 +7129,7 @@ var listSessionsRoute = createRoute2({
|
|
|
5498
7129
|
var cancelSessionRoute = createRoute2({
|
|
5499
7130
|
method: "post",
|
|
5500
7131
|
path: "/{session_id}/cancel",
|
|
5501
|
-
tags: [
|
|
7132
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5502
7133
|
summary: "Cancel a running turn in a session",
|
|
5503
7134
|
description: "Cancel the running last turn for a session. Only the session creator (`created_by`) may cancel.",
|
|
5504
7135
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5532,7 +7163,7 @@ var cancelSessionRoute = createRoute2({
|
|
|
5532
7163
|
var listSessionEventsRoute = createRoute2({
|
|
5533
7164
|
method: "get",
|
|
5534
7165
|
path: "/{session_id}/events",
|
|
5535
|
-
tags: [
|
|
7166
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
5536
7167
|
summary: "List session events",
|
|
5537
7168
|
description: "List session events as `{ turn_id, event }` across the active turn branch (newest first), including persisted events from a running tip. Each turn contributes turn.created, content events (model.message, tool.call, \u2026), and turn.done when terminal; streaming deltas are not included. Use `page_token` to paginate backward toward older events while retaining the original branch anchor. Only the session creator (`created_by`) may list events.",
|
|
5538
7169
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -5564,9 +7195,9 @@ var listSessionEventsRoute = createRoute2({
|
|
|
5564
7195
|
|
|
5565
7196
|
// src/runtime/peeringIds.ts
|
|
5566
7197
|
import { HTTPException as HTTPException2 } from "hono/http-exception";
|
|
5567
|
-
import { ulid } from "ulid";
|
|
7198
|
+
import { ulid as ulid3 } from "ulid";
|
|
5568
7199
|
function mintPeeredTurnId(executorId) {
|
|
5569
|
-
return `${
|
|
7200
|
+
return `${ulid3().toLowerCase()}.${executorId}`;
|
|
5570
7201
|
}
|
|
5571
7202
|
function executorFromTurnId(turnId) {
|
|
5572
7203
|
const parts = turnId.split(".");
|
|
@@ -5579,10 +7210,10 @@ function executorFromTurnId(turnId) {
|
|
|
5579
7210
|
// src/apis/sessions.ts
|
|
5580
7211
|
var TENANT_ID = "default";
|
|
5581
7212
|
var SESSIONS_CANCEL_PATH = "sessions/cancel";
|
|
5582
|
-
var CancelPeerBodySchema =
|
|
5583
|
-
session_id:
|
|
5584
|
-
turn_id:
|
|
5585
|
-
reason:
|
|
7213
|
+
var CancelPeerBodySchema = z13.object({
|
|
7214
|
+
session_id: z13.string(),
|
|
7215
|
+
turn_id: z13.string(),
|
|
7216
|
+
reason: z13.enum(CancellationReason)
|
|
5586
7217
|
});
|
|
5587
7218
|
function toWireSession(record) {
|
|
5588
7219
|
return {
|
|
@@ -5683,7 +7314,7 @@ function checkSessionAccess({ userRef, createdBy }) {
|
|
|
5683
7314
|
function createSessionsRouter(deps) {
|
|
5684
7315
|
const createSessionHandler = async (c) => {
|
|
5685
7316
|
const body = c.req.valid("json");
|
|
5686
|
-
const sessionId =
|
|
7317
|
+
const sessionId = ulid4().toLowerCase();
|
|
5687
7318
|
if (isSessionAgentNameRef(body.agent)) {
|
|
5688
7319
|
const agent = await deps.agentStore.getAgent({ tenant_id: TENANT_ID, name: body.agent.name });
|
|
5689
7320
|
if (agent === void 0) {
|
|
@@ -5944,13 +7575,13 @@ import { extractErrorLogFields as extractErrorLogFields2 } from "@truefoundry/tr
|
|
|
5944
7575
|
// src/auth/cookies.ts
|
|
5945
7576
|
init_config();
|
|
5946
7577
|
import { deleteCookie, getCookie, setCookie } from "hono/cookie";
|
|
5947
|
-
import { z as
|
|
7578
|
+
import { z as z14 } from "zod";
|
|
5948
7579
|
var OAUTH_STATE_COOKIE = "oauth_state";
|
|
5949
7580
|
var ID_TOKEN_COOKIE = "id_token";
|
|
5950
|
-
var OAuthStateSchema =
|
|
5951
|
-
state:
|
|
5952
|
-
code_verifier:
|
|
5953
|
-
return_to:
|
|
7581
|
+
var OAuthStateSchema = z14.object({
|
|
7582
|
+
state: z14.string(),
|
|
7583
|
+
code_verifier: z14.string(),
|
|
7584
|
+
return_to: z14.string()
|
|
5954
7585
|
});
|
|
5955
7586
|
function getAuthCookieAttributes() {
|
|
5956
7587
|
return {
|
|
@@ -6044,6 +7675,18 @@ function buildAuthorizationRequestParams(config) {
|
|
|
6044
7675
|
};
|
|
6045
7676
|
}
|
|
6046
7677
|
|
|
7678
|
+
// src/auth/safeReturnTo.ts
|
|
7679
|
+
var SAFE_RETURN_TO = /^\/(?!\/|api(?:\/|$)).*/;
|
|
7680
|
+
function isSafeReturnTo(value) {
|
|
7681
|
+
return SAFE_RETURN_TO.test(value);
|
|
7682
|
+
}
|
|
7683
|
+
function safeReturnTo(value) {
|
|
7684
|
+
if (value && isSafeReturnTo(value)) {
|
|
7685
|
+
return value;
|
|
7686
|
+
}
|
|
7687
|
+
return "/";
|
|
7688
|
+
}
|
|
7689
|
+
|
|
6047
7690
|
// src/auth/oidc.ts
|
|
6048
7691
|
var CALLBACK_PATH = "/api/v1/auth/callback";
|
|
6049
7692
|
var OAUTH_STATE_MAX_AGE_SECONDS = 10 * 60;
|
|
@@ -6080,13 +7723,6 @@ function authCallbackUrl() {
|
|
|
6080
7723
|
const publicBaseUrl = getPublicBaseUrl();
|
|
6081
7724
|
return `${publicBaseUrl}${CALLBACK_PATH}`;
|
|
6082
7725
|
}
|
|
6083
|
-
var SAFE_RETURN_TO = /^\/(?!\/|api(?:\/|$)).*/;
|
|
6084
|
-
function safeReturnTo(value) {
|
|
6085
|
-
if (value && SAFE_RETURN_TO.test(value)) {
|
|
6086
|
-
return value;
|
|
6087
|
-
}
|
|
6088
|
-
return "/";
|
|
6089
|
-
}
|
|
6090
7726
|
async function buildLoginAuthorization(params) {
|
|
6091
7727
|
const oidcConfig = getOidcVerify()?.oidcConfig;
|
|
6092
7728
|
if (!oidcConfig) {
|
|
@@ -6242,36 +7878,36 @@ var adminAuthMiddleware = async (c, next) => {
|
|
|
6242
7878
|
import { createRoute as createRoute3 } from "@hono/zod-openapi";
|
|
6243
7879
|
|
|
6244
7880
|
// src/schemas/auth.ts
|
|
6245
|
-
import { z as
|
|
6246
|
-
var AuthLoginQuerySchema =
|
|
6247
|
-
return_to:
|
|
7881
|
+
import { z as z15 } from "@hono/zod-openapi";
|
|
7882
|
+
var AuthLoginQuerySchema = z15.object({
|
|
7883
|
+
return_to: z15.string().optional().describe('Path to return to after login. Must be a same-origin relative path; anything else falls back to "/".')
|
|
6248
7884
|
});
|
|
6249
|
-
var OAuthCallbackQuerySchema =
|
|
6250
|
-
code:
|
|
6251
|
-
state:
|
|
6252
|
-
error:
|
|
6253
|
-
error_description:
|
|
7885
|
+
var OAuthCallbackQuerySchema = z15.object({
|
|
7886
|
+
code: z15.string().min(1).optional().describe("Authorization code, present when the user granted consent."),
|
|
7887
|
+
state: z15.string().min(1).describe("Opaque token; correlates this callback to its pending login. Always present, success or error."),
|
|
7888
|
+
error: z15.string().optional().describe("Error code from the identity provider, present instead of `code` if the user denied consent."),
|
|
7889
|
+
error_description: z15.string().optional().describe("Human-readable error detail from the identity provider when `error` is set.")
|
|
6254
7890
|
});
|
|
6255
|
-
var OAuthCallbackSuccessSchema =
|
|
6256
|
-
success:
|
|
7891
|
+
var OAuthCallbackSuccessSchema = z15.object({
|
|
7892
|
+
success: z15.literal(true).describe("Present when the OAuth callback completed without a return_to.")
|
|
6257
7893
|
});
|
|
6258
|
-
var
|
|
6259
|
-
type:
|
|
7894
|
+
var GetMeResponseSchema = z15.object({
|
|
7895
|
+
type: z15.enum(["default", "oidc-connected"]).describe(
|
|
6260
7896
|
"Session kind: `default` when no valid OIDC session; `oidc-connected` after a successful browser login."
|
|
6261
7897
|
),
|
|
6262
|
-
email:
|
|
6263
|
-
role:
|
|
6264
|
-
}).openapi("
|
|
7898
|
+
email: z15.string().describe('User email from the ID token when connected; `"default"` when anonymous.'),
|
|
7899
|
+
role: z15.string().describe("Caller role.")
|
|
7900
|
+
}).openapi("GetMeResponse");
|
|
6265
7901
|
|
|
6266
7902
|
// src/routes/authRoutes.ts
|
|
6267
|
-
var AUTH_TAG = "Auth";
|
|
6268
7903
|
var authLoginRoute = createRoute3({
|
|
6269
7904
|
method: "get",
|
|
6270
7905
|
path: "/login",
|
|
6271
|
-
tags: [
|
|
7906
|
+
tags: ["Auth" /* AUTH */],
|
|
6272
7907
|
summary: "Start the login flow",
|
|
6273
7908
|
description: "Redirects the browser to the configured identity provider. In local/single-binary mode, redirects straight back into the app \u2014 there is nothing to log into.",
|
|
6274
7909
|
"x-fern-ignore": true,
|
|
7910
|
+
"x-excluded": true,
|
|
6275
7911
|
request: { query: AuthLoginQuerySchema },
|
|
6276
7912
|
responses: {
|
|
6277
7913
|
302: { description: "Redirect to the IdP authorization endpoint." }
|
|
@@ -6280,10 +7916,11 @@ var authLoginRoute = createRoute3({
|
|
|
6280
7916
|
var oAuthCallbackRoute = createRoute3({
|
|
6281
7917
|
method: "get",
|
|
6282
7918
|
path: "/callback",
|
|
6283
|
-
tags: [
|
|
7919
|
+
tags: ["Auth" /* AUTH */],
|
|
6284
7920
|
summary: "Login callback",
|
|
6285
7921
|
description: "Browser-redirect target hit by the identity provider after login, never called directly by SDK consumers. In local/single-binary mode, redirects straight back into the app.",
|
|
6286
7922
|
"x-fern-ignore": true,
|
|
7923
|
+
"x-excluded": true,
|
|
6287
7924
|
request: { query: OAuthCallbackQuerySchema },
|
|
6288
7925
|
responses: {
|
|
6289
7926
|
302: { description: "Redirect back into the app on success, or to /?error=<reason> on failure." }
|
|
@@ -6292,11 +7929,11 @@ var oAuthCallbackRoute = createRoute3({
|
|
|
6292
7929
|
var authLogoutRoute = createRoute3({
|
|
6293
7930
|
method: "post",
|
|
6294
7931
|
path: "/logout",
|
|
6295
|
-
tags: [
|
|
7932
|
+
tags: ["Auth" /* AUTH */],
|
|
6296
7933
|
summary: "Clear the local session",
|
|
6297
7934
|
description: "Ends the local harness session only \u2014 does not hit the IdP end-session endpoint. A no-op in local/single-binary mode, since there is no real session to clear.",
|
|
6298
|
-
"x-fern-
|
|
6299
|
-
"x-
|
|
7935
|
+
"x-fern-ignore": true,
|
|
7936
|
+
"x-excluded": true,
|
|
6300
7937
|
responses: {
|
|
6301
7938
|
204: { description: "Session cookie cleared." }
|
|
6302
7939
|
}
|
|
@@ -6304,14 +7941,14 @@ var authLogoutRoute = createRoute3({
|
|
|
6304
7941
|
var meRoute = createRoute3({
|
|
6305
7942
|
method: "get",
|
|
6306
7943
|
path: "/me",
|
|
6307
|
-
tags: [
|
|
7944
|
+
tags: ["Auth" /* AUTH */],
|
|
6308
7945
|
summary: "Current session",
|
|
6309
7946
|
description: "Returns the authenticated caller identity. When auth is enabled this requires a valid `id_token` cookie or `Authorization: Bearer` ID token (401 otherwise). When auth is disabled, returns the default identity.",
|
|
6310
7947
|
"x-fern-sdk-group-name": ["auth"],
|
|
6311
7948
|
"x-fern-sdk-method-name": "me",
|
|
6312
7949
|
responses: {
|
|
6313
7950
|
200: {
|
|
6314
|
-
content: { "application/json": { schema:
|
|
7951
|
+
content: { "application/json": { schema: GetMeResponseSchema } },
|
|
6315
7952
|
description: "Session type and identity for the current request."
|
|
6316
7953
|
},
|
|
6317
7954
|
401: {
|
|
@@ -6351,6 +7988,9 @@ function createAuthRouter(params) {
|
|
|
6351
7988
|
const pending = readOAuthStateCookie({ context: c, logger: params.logger });
|
|
6352
7989
|
clearAuthCookie({ context: c, name: OAUTH_STATE_COOKIE });
|
|
6353
7990
|
if (pending?.state !== query.state || query.error || !query.code) {
|
|
7991
|
+
if (await resolveAuthUser(c)) {
|
|
7992
|
+
return c.redirect("/", 302);
|
|
7993
|
+
}
|
|
6354
7994
|
const description = query.error_description?.trim();
|
|
6355
7995
|
const reason = query.error && description ? description : "login_failed";
|
|
6356
7996
|
return c.redirect(oauthErrorRedirect(reason), 302);
|
|
@@ -6366,6 +8006,9 @@ function createAuthRouter(params) {
|
|
|
6366
8006
|
return c.redirect(safeReturnTo(pending.return_to), 302);
|
|
6367
8007
|
} catch (error) {
|
|
6368
8008
|
params.logger.error("Failed to exchange authorization code", extractErrorLogFields2(error));
|
|
8009
|
+
if (await resolveAuthUser(c)) {
|
|
8010
|
+
return c.redirect(safeReturnTo(pending.return_to), 302);
|
|
8011
|
+
}
|
|
6369
8012
|
const reason = error instanceof Error ? error.message : "login_failed";
|
|
6370
8013
|
return c.redirect(oauthErrorRedirect(reason), 302);
|
|
6371
8014
|
}
|
|
@@ -6390,25 +8033,29 @@ import { OpenAPIHono as OpenAPIHono4 } from "@hono/zod-openapi";
|
|
|
6390
8033
|
import { extractErrorLogFields as extractErrorLogFields3 } from "@truefoundry/trueforge-core/core";
|
|
6391
8034
|
|
|
6392
8035
|
// src/routes/capabilityRoutes.ts
|
|
6393
|
-
import { createRoute as createRoute4, z as
|
|
6394
|
-
var
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
8036
|
+
import { createRoute as createRoute4, z as z16 } from "@hono/zod-openapi";
|
|
8037
|
+
var SandboxCapabilitySchema = z16.object({
|
|
8038
|
+
enabled: z16.boolean().describe("Whether a sandbox provider is configured for this tenant.")
|
|
8039
|
+
}).openapi("SandboxCapability");
|
|
8040
|
+
var SkillCapabilitySchema = z16.object({
|
|
8041
|
+
enabled: z16.boolean().describe("Whether skills are available. False when sandbox is not enabled (skills require a sandbox)."),
|
|
8042
|
+
reason: z16.string().optional().describe("Present when skills are disabled. Explains why.")
|
|
8043
|
+
}).openapi("SkillCapability");
|
|
8044
|
+
var SettingsCapabilitySchema = z16.object({
|
|
8045
|
+
enabled: z16.boolean().describe("Whether the settings UI/API is enabled.")
|
|
8046
|
+
}).openapi("SettingsCapability");
|
|
8047
|
+
var CapabilitiesDataSchema = z16.object({
|
|
8048
|
+
sandbox: SandboxCapabilitySchema,
|
|
8049
|
+
skill: SkillCapabilitySchema,
|
|
8050
|
+
settings: SettingsCapabilitySchema
|
|
8051
|
+
}).openapi("CapabilitiesData");
|
|
8052
|
+
var GetCapabilitiesResponseSchema = z16.object({
|
|
8053
|
+
data: CapabilitiesDataSchema
|
|
6407
8054
|
}).openapi("GetCapabilitiesResponse");
|
|
6408
8055
|
var getCapabilitiesRoute = createRoute4({
|
|
6409
8056
|
method: "get",
|
|
6410
8057
|
path: "/",
|
|
6411
|
-
tags: ["Capabilities"],
|
|
8058
|
+
tags: ["Capabilities" /* CAPABILITIES */],
|
|
6412
8059
|
summary: "Get server capabilities",
|
|
6413
8060
|
"x-fern-sdk-group-name": ["server"],
|
|
6414
8061
|
"x-fern-sdk-method-name": "get_capabilities",
|
|
@@ -6446,7 +8093,7 @@ function createCapabilitiesRouter(deps) {
|
|
|
6446
8093
|
} catch (error) {
|
|
6447
8094
|
deps.logger.warn("Sandbox image status check failed; reporting sandbox disabled", extractErrorLogFields3(error));
|
|
6448
8095
|
}
|
|
6449
|
-
const sandboxEnabled = status === "ready";
|
|
8096
|
+
const sandboxEnabled = status === "ready" || status === void 0 && isLocalSandboxFallbackEnabled();
|
|
6450
8097
|
const settingsEnabled = isAdmin(resolveUserContext(c));
|
|
6451
8098
|
return c.json(
|
|
6452
8099
|
{
|
|
@@ -6470,42 +8117,42 @@ import { SUPPORTED_REASONING_EFFORTS as SUPPORTED_REASONING_EFFORTS2 } from "@tr
|
|
|
6470
8117
|
import { createRoute as createRoute5 } from "@hono/zod-openapi";
|
|
6471
8118
|
|
|
6472
8119
|
// src/schemas/mcpCatalog.ts
|
|
6473
|
-
import { z as
|
|
6474
|
-
var CatalogMcpServerSchema =
|
|
8120
|
+
import { z as z17 } from "@hono/zod-openapi";
|
|
8121
|
+
var CatalogMcpServerSchema = z17.object({
|
|
6475
8122
|
type: McpServerTypeSchema,
|
|
6476
8123
|
name: NameSchema,
|
|
6477
|
-
logo:
|
|
6478
|
-
url:
|
|
8124
|
+
logo: z17.url().optional().describe("URL of the MCP server logo asset."),
|
|
8125
|
+
url: z17.url().describe("URL of the remote MCP server."),
|
|
6479
8126
|
description: McpServerDescriptionSchema,
|
|
6480
8127
|
auth: McpServerManifestAuthSchema.optional()
|
|
6481
|
-
}).strict().openapi("
|
|
6482
|
-
var McpCatalogFileSchema =
|
|
6483
|
-
mcp_servers:
|
|
8128
|
+
}).strict().openapi("CatalogMCPServer");
|
|
8129
|
+
var McpCatalogFileSchema = z17.object({
|
|
8130
|
+
mcp_servers: z17.array(CatalogMcpServerSchema)
|
|
6484
8131
|
}).strict().superRefine((file, ctx) => {
|
|
6485
8132
|
uniqueNames(file.mcp_servers, ctx);
|
|
6486
8133
|
});
|
|
6487
|
-
var GetMcpServerCatalogResponseSchema =
|
|
6488
|
-
data:
|
|
6489
|
-
}).openapi("
|
|
8134
|
+
var GetMcpServerCatalogResponseSchema = z17.object({
|
|
8135
|
+
data: z17.array(CatalogMcpServerSchema)
|
|
8136
|
+
}).openapi("GetMCPServerCatalogResponse");
|
|
6490
8137
|
|
|
6491
8138
|
// src/schemas/modelCatalog.ts
|
|
6492
|
-
import { z as
|
|
8139
|
+
import { z as z19 } from "@hono/zod-openapi";
|
|
6493
8140
|
|
|
6494
8141
|
// src/schemas/modelProvider.ts
|
|
6495
|
-
import { z as
|
|
8142
|
+
import { z as z18 } from "@hono/zod-openapi";
|
|
6496
8143
|
import { SUPPORTED_REASONING_EFFORTS, VERCEL_AI_PROVIDER_NAMES } from "@truefoundry/trueforge-core/core";
|
|
6497
|
-
var ModelProviderTypeSchema =
|
|
6498
|
-
var ReasoningEffortSchema =
|
|
6499
|
-
var ModelPropertiesSchema =
|
|
6500
|
-
context_length:
|
|
6501
|
-
max_output_tokens:
|
|
6502
|
-
reasoning_efforts:
|
|
8144
|
+
var ModelProviderTypeSchema = z18.enum(VERCEL_AI_PROVIDER_NAMES).openapi("ModelProviderType");
|
|
8145
|
+
var ReasoningEffortSchema = z18.enum(SUPPORTED_REASONING_EFFORTS).openapi("ReasoningEffort");
|
|
8146
|
+
var ModelPropertiesSchema = z18.object({
|
|
8147
|
+
context_length: z18.number().int().positive().optional().describe("Maximum context window size in tokens."),
|
|
8148
|
+
max_output_tokens: z18.number().int().positive().optional().describe("Maximum output tokens the model can generate."),
|
|
8149
|
+
reasoning_efforts: z18.array(ReasoningEffortSchema).min(1).optional().describe("Supported reasoning-effort values for this model.")
|
|
6503
8150
|
}).strict().describe("Optional model capability metadata.").openapi("ModelProperties");
|
|
6504
|
-
var
|
|
6505
|
-
model_id:
|
|
8151
|
+
var ConfiguredModelSchema = z18.object({
|
|
8152
|
+
model_id: z18.string().min(1).describe("Upstream, provider-specific identifier sent to the provider API."),
|
|
6506
8153
|
name: NameSchema,
|
|
6507
8154
|
properties: ModelPropertiesSchema
|
|
6508
|
-
}).strict().openapi("
|
|
8155
|
+
}).strict().openapi("ConfiguredModel");
|
|
6509
8156
|
function refineUniqueModels(models, ctx) {
|
|
6510
8157
|
uniqueNames(models, ctx);
|
|
6511
8158
|
const seenModelIds = /* @__PURE__ */ new Set();
|
|
@@ -6520,22 +8167,22 @@ function refineUniqueModels(models, ctx) {
|
|
|
6520
8167
|
seenModelIds.add(model.model_id);
|
|
6521
8168
|
}
|
|
6522
8169
|
}
|
|
6523
|
-
var ModelProviderAuthSchema =
|
|
6524
|
-
api_key:
|
|
8170
|
+
var ModelProviderAuthSchema = z18.object({
|
|
8171
|
+
api_key: z18.string().min(1).describe(
|
|
6525
8172
|
"Provider API key. Responses are redacted; on PUT, a real value sets/rotates and a redacted value keeps the stored key."
|
|
6526
8173
|
)
|
|
6527
8174
|
}).strict().describe("Provider authentication credentials.").openapi("ModelProviderAuth");
|
|
6528
|
-
var ModelProviderManifestBaseSchema =
|
|
8175
|
+
var ModelProviderManifestBaseSchema = z18.object({
|
|
6529
8176
|
auth: ModelProviderAuthSchema,
|
|
6530
|
-
models:
|
|
8177
|
+
models: z18.array(ConfiguredModelSchema).min(1).describe("Models exposed by this provider (at least one).")
|
|
6531
8178
|
}).strict();
|
|
6532
8179
|
function wellKnownProviderSchema({
|
|
6533
8180
|
type,
|
|
6534
8181
|
base_url
|
|
6535
8182
|
}) {
|
|
6536
8183
|
return ModelProviderManifestBaseSchema.extend({
|
|
6537
|
-
type:
|
|
6538
|
-
base_url:
|
|
8184
|
+
type: z18.literal(type),
|
|
8185
|
+
base_url: z18.url().default(base_url).describe("Override of the provider's default API base URL.")
|
|
6539
8186
|
}).strict();
|
|
6540
8187
|
}
|
|
6541
8188
|
var OpenAiModelProviderSchema = wellKnownProviderSchema({
|
|
@@ -6571,15 +8218,15 @@ var AlibabaModelProviderSchema = wellKnownProviderSchema({
|
|
|
6571
8218
|
base_url: "https://dashscope-intl.aliyuncs.com/compatible-mode/v1"
|
|
6572
8219
|
}).openapi("AlibabaModelProvider");
|
|
6573
8220
|
var CustomModelProviderSchema = ModelProviderManifestBaseSchema.extend({
|
|
6574
|
-
type:
|
|
8221
|
+
type: z18.literal("custom"),
|
|
6575
8222
|
name: NameSchema,
|
|
6576
|
-
base_url:
|
|
8223
|
+
base_url: z18.url().describe("Base URL of the provider's API."),
|
|
6577
8224
|
auth: ModelProviderAuthSchema.optional()
|
|
6578
8225
|
}).strict().openapi("CustomModelProvider");
|
|
6579
8226
|
function refineModelProviderManifest(manifest, ctx) {
|
|
6580
8227
|
refineUniqueModels(manifest.models, ctx);
|
|
6581
8228
|
}
|
|
6582
|
-
var ModelProviderBodySchema =
|
|
8229
|
+
var ModelProviderBodySchema = z18.discriminatedUnion("type", [
|
|
6583
8230
|
OpenAiModelProviderSchema,
|
|
6584
8231
|
AnthropicModelProviderSchema,
|
|
6585
8232
|
GoogleGeminiModelProviderSchema,
|
|
@@ -6594,87 +8241,87 @@ var ModelProviderManifestSchema = ModelProviderBodySchema.openapi("ModelProvider
|
|
|
6594
8241
|
function modelProviderName(provider) {
|
|
6595
8242
|
return provider.type === "custom" ? provider.name : provider.type;
|
|
6596
8243
|
}
|
|
6597
|
-
var
|
|
8244
|
+
var ConfiguredModelProviderSchema = z18.object({
|
|
6598
8245
|
name: NameSchema,
|
|
6599
8246
|
manifest: ModelProviderManifestSchema
|
|
6600
|
-
}).strict().openapi("
|
|
6601
|
-
var CreateModelProviderRequestSchema =
|
|
8247
|
+
}).strict().openapi("ConfiguredModelProvider");
|
|
8248
|
+
var CreateModelProviderRequestSchema = z18.object({
|
|
6602
8249
|
manifest: ModelProviderManifestSchema
|
|
6603
8250
|
}).strict().openapi("CreateModelProviderRequest");
|
|
6604
|
-
var
|
|
8251
|
+
var UpdateModelProviderRequestSchema = z18.object({
|
|
6605
8252
|
manifest: ModelProviderManifestSchema
|
|
6606
|
-
}).strict().openapi("
|
|
6607
|
-
var GetModelProviderResponseSchema =
|
|
6608
|
-
data:
|
|
8253
|
+
}).strict().openapi("UpdateModelProviderRequest");
|
|
8254
|
+
var GetModelProviderResponseSchema = z18.object({
|
|
8255
|
+
data: ConfiguredModelProviderSchema
|
|
6609
8256
|
}).openapi("GetModelProviderResponse");
|
|
6610
|
-
var ListModelProvidersResponseSchema =
|
|
6611
|
-
data:
|
|
8257
|
+
var ListModelProvidersResponseSchema = z18.object({
|
|
8258
|
+
data: z18.array(ConfiguredModelProviderSchema)
|
|
6612
8259
|
}).openapi("ListModelProvidersResponse");
|
|
6613
|
-
var
|
|
6614
|
-
name:
|
|
6615
|
-
}).strict().describe("Owning configured provider.").openapi("
|
|
6616
|
-
var
|
|
6617
|
-
name:
|
|
6618
|
-
model_id:
|
|
6619
|
-
provider:
|
|
8260
|
+
var AvailableModelProviderSchema = z18.object({
|
|
8261
|
+
name: z18.string().min(1).describe("Configured provider resource name; matches the FQN prefix of `name`.")
|
|
8262
|
+
}).strict().describe("Owning configured provider.").openapi("AvailableModelProvider");
|
|
8263
|
+
var AvailableModelSchema = z18.object({
|
|
8264
|
+
name: z18.string().describe('Fully qualified name `provider_name/model_name`, e.g. "openai/gpt-5-6-sol". Unique within a tenant.'),
|
|
8265
|
+
model_id: z18.string().describe("Upstream, provider-specific identifier sent to the provider API."),
|
|
8266
|
+
provider: AvailableModelProviderSchema,
|
|
6620
8267
|
properties: ModelPropertiesSchema
|
|
6621
|
-
}).strict().openapi("
|
|
6622
|
-
var
|
|
6623
|
-
data:
|
|
6624
|
-
}).openapi("
|
|
8268
|
+
}).strict().openapi("AvailableModel");
|
|
8269
|
+
var ListAvailableModelsResponseSchema = z18.object({
|
|
8270
|
+
data: z18.array(AvailableModelSchema)
|
|
8271
|
+
}).openapi("ListAvailableModelsResponse");
|
|
6625
8272
|
|
|
6626
8273
|
// src/schemas/modelCatalog.ts
|
|
6627
8274
|
var CatalogWellKnownModelProviderTypeSchema = ModelProviderTypeSchema.exclude(["custom"]).openapi(
|
|
6628
8275
|
"CatalogWellKnownModelProviderType"
|
|
6629
8276
|
);
|
|
6630
|
-
var
|
|
8277
|
+
var CatalogModelSchema = ConfiguredModelSchema.openapi("CatalogModel");
|
|
8278
|
+
var CatalogWellKnownModelProviderSchema = z19.object({
|
|
6631
8279
|
type: CatalogWellKnownModelProviderTypeSchema,
|
|
6632
|
-
logo:
|
|
6633
|
-
models:
|
|
8280
|
+
logo: z19.url().optional().describe("URL of the provider logo asset"),
|
|
8281
|
+
models: z19.array(CatalogModelSchema).describe("Preset models")
|
|
6634
8282
|
}).strict().openapi("CatalogWellKnownModelProvider");
|
|
6635
|
-
var CatalogCustomModelProviderSchema =
|
|
6636
|
-
type:
|
|
6637
|
-
supported_reasoning_efforts:
|
|
8283
|
+
var CatalogCustomModelProviderSchema = z19.object({
|
|
8284
|
+
type: z19.literal("custom"),
|
|
8285
|
+
supported_reasoning_efforts: z19.array(ReasoningEffortSchema).describe("Supported reasoning-effort values for this provider")
|
|
6638
8286
|
}).strict().openapi("CatalogCustomModelProvider");
|
|
6639
|
-
var CatalogModelProviderSchema =
|
|
6640
|
-
var ModelCatalogFileSchema =
|
|
6641
|
-
providers:
|
|
8287
|
+
var CatalogModelProviderSchema = z19.union([CatalogWellKnownModelProviderSchema, CatalogCustomModelProviderSchema]).openapi("CatalogModelProvider");
|
|
8288
|
+
var ModelCatalogFileSchema = z19.object({
|
|
8289
|
+
providers: z19.array(CatalogWellKnownModelProviderSchema)
|
|
6642
8290
|
}).strict().superRefine((file, ctx) => {
|
|
6643
8291
|
uniqueTypes(file.providers, ctx);
|
|
6644
8292
|
for (const provider of file.providers) {
|
|
6645
8293
|
refineUniqueModels(provider.models, ctx);
|
|
6646
8294
|
}
|
|
6647
8295
|
});
|
|
6648
|
-
var GetModelProviderCatalogResponseSchema =
|
|
6649
|
-
data:
|
|
8296
|
+
var GetModelProviderCatalogResponseSchema = z19.object({
|
|
8297
|
+
data: z19.array(CatalogModelProviderSchema)
|
|
6650
8298
|
}).openapi("GetModelProviderCatalogResponse");
|
|
6651
8299
|
|
|
6652
8300
|
// src/schemas/sandboxCatalog.ts
|
|
6653
|
-
import { z as
|
|
6654
|
-
var
|
|
6655
|
-
var
|
|
6656
|
-
|
|
6657
|
-
providers: z18.array(CatalogSandboxProviderSchema)
|
|
8301
|
+
import { z as z20 } from "@hono/zod-openapi";
|
|
8302
|
+
var CatalogSandboxProviderSchema = DaytonaSandboxProviderSchema.omit({ auth: true }).strict().openapi("CatalogSandboxProvider");
|
|
8303
|
+
var SandboxCatalogFileSchema = z20.object({
|
|
8304
|
+
providers: z20.array(CatalogSandboxProviderSchema)
|
|
6658
8305
|
}).strict();
|
|
6659
|
-
var GetSandboxProviderCatalogResponseSchema =
|
|
6660
|
-
data:
|
|
8306
|
+
var GetSandboxProviderCatalogResponseSchema = z20.object({
|
|
8307
|
+
data: z20.array(CatalogSandboxProviderSchema)
|
|
6661
8308
|
}).openapi("GetSandboxProviderCatalogResponse");
|
|
6662
8309
|
|
|
6663
8310
|
// src/schemas/skillCatalog.ts
|
|
6664
|
-
import { z as
|
|
8311
|
+
import { z as z22 } from "@hono/zod-openapi";
|
|
6665
8312
|
|
|
6666
8313
|
// src/schemas/skill.ts
|
|
6667
|
-
import { z as
|
|
6668
|
-
var SkillTypeSchema =
|
|
8314
|
+
import { z as z21 } from "@hono/zod-openapi";
|
|
8315
|
+
var SkillTypeSchema = z21.enum(["git"]).openapi("SkillType");
|
|
6669
8316
|
var GIT_URL_REGEX = /^https:\/\/(github\.com\/[A-Za-z0-9._-]+\/[A-Za-z0-9._-]+|gitlab\.com\/[A-Za-z0-9._-]+(?:\/[A-Za-z0-9._-]+)+)(\/|\.git)?$/;
|
|
6670
8317
|
var GIT_REF_REGEX = /^[A-Za-z0-9._\-/]+$/;
|
|
6671
8318
|
var SKILL_PATH_REGEX = /^[A-Za-z0-9._\-/]+$/;
|
|
6672
8319
|
var hasParentTraversal = (value) => value.split("/").includes("..");
|
|
6673
|
-
var SkillGitUrlSchema =
|
|
6674
|
-
var SkillGitPathSchema =
|
|
6675
|
-
var SkillGitRefSchema =
|
|
6676
|
-
var SkillDescriptionSchema =
|
|
6677
|
-
var SkillManifestObjectSchema =
|
|
8320
|
+
var SkillGitUrlSchema = z21.string().trim().min(1).regex(GIT_URL_REGEX, "Must be a GitHub or GitLab HTTPS URL").refine((v) => !hasParentTraversal(v), 'URL must not contain ".." segments').describe("Full HTTPS URL of a GitHub or GitLab repository.");
|
|
8321
|
+
var SkillGitPathSchema = z21.string().trim().min(1).regex(SKILL_PATH_REGEX, 'Path may only contain letters, numbers, ".", "_", "-", and "/"').refine((v) => !hasParentTraversal(v), 'Path must not contain ".." segments').refine((v) => !v.split("/").includes("."), 'Path must not contain "." segments').refine((v) => v.replace(/^\/+|\/+$/g, "").length > 0, "Path must reference a subdirectory, not only slashes").describe("Path to the skill directory within the repository. Omit to use the repository root.");
|
|
8322
|
+
var SkillGitRefSchema = z21.string().trim().min(1).regex(GIT_REF_REGEX, 'Ref may only contain letters, numbers, ".", "_", "-", and "/"').refine((v) => !hasParentTraversal(v), 'Ref must not contain ".." segments').refine((v) => v.replace(/^\/+|\/+$/g, "").length > 0, "Ref must not consist only of slashes").describe("Git ref \u2014 branch name, tag, or commit SHA.");
|
|
8323
|
+
var SkillDescriptionSchema = z21.string().trim().min(1).describe("Concise guidance for when the agent should use the skill.");
|
|
8324
|
+
var SkillManifestObjectSchema = z21.object({
|
|
6678
8325
|
type: SkillTypeSchema,
|
|
6679
8326
|
name: NameSchema,
|
|
6680
8327
|
url: SkillGitUrlSchema,
|
|
@@ -6683,26 +8330,26 @@ var SkillManifestObjectSchema = z19.object({
|
|
|
6683
8330
|
description: SkillDescriptionSchema
|
|
6684
8331
|
}).strict();
|
|
6685
8332
|
var SkillManifestSchema = SkillManifestObjectSchema.openapi("SkillManifest");
|
|
6686
|
-
var ConfiguredSkillSchema =
|
|
8333
|
+
var ConfiguredSkillSchema = z21.object({
|
|
6687
8334
|
name: NameSchema,
|
|
6688
8335
|
manifest: SkillManifestSchema
|
|
6689
8336
|
}).strict().openapi("ConfiguredSkill");
|
|
6690
|
-
var CreateSkillRequestSchema =
|
|
8337
|
+
var CreateSkillRequestSchema = z21.object({
|
|
6691
8338
|
manifest: SkillManifestSchema
|
|
6692
8339
|
}).strict().openapi("CreateSkillRequest");
|
|
6693
|
-
var
|
|
8340
|
+
var UpdateSkillRequestSchema = z21.object({
|
|
6694
8341
|
manifest: SkillManifestSchema
|
|
6695
|
-
}).strict().openapi("
|
|
6696
|
-
var GetSkillResponseSchema =
|
|
6697
|
-
var ListSkillsResponseSchema =
|
|
6698
|
-
var
|
|
8342
|
+
}).strict().openapi("UpdateSkillRequest");
|
|
8343
|
+
var GetSkillResponseSchema = z21.object({ data: ConfiguredSkillSchema }).openapi("GetSkillResponse");
|
|
8344
|
+
var ListSkillsResponseSchema = z21.object({ data: z21.array(ConfiguredSkillSchema) }).openapi("ListSkillsResponse");
|
|
8345
|
+
var AvailableSkillSchema = z21.object({
|
|
6699
8346
|
name: NameSchema,
|
|
6700
8347
|
description: SkillDescriptionSchema
|
|
6701
|
-
}).strict().openapi("
|
|
6702
|
-
var ListAvailableSkillsResponseSchema =
|
|
8348
|
+
}).strict().openapi("AvailableSkill");
|
|
8349
|
+
var ListAvailableSkillsResponseSchema = z21.object({ data: z21.array(AvailableSkillSchema) }).openapi("ListAvailableSkillsResponse");
|
|
6703
8350
|
|
|
6704
8351
|
// src/schemas/skillCatalog.ts
|
|
6705
|
-
var CatalogSkillSchema =
|
|
8352
|
+
var CatalogSkillSchema = z22.object({
|
|
6706
8353
|
type: SkillTypeSchema,
|
|
6707
8354
|
name: NameSchema,
|
|
6708
8355
|
url: SkillGitUrlSchema,
|
|
@@ -6710,24 +8357,23 @@ var CatalogSkillSchema = z20.object({
|
|
|
6710
8357
|
ref: SkillGitRefSchema,
|
|
6711
8358
|
description: SkillDescriptionSchema
|
|
6712
8359
|
}).strict().openapi("CatalogSkill");
|
|
6713
|
-
var SkillCatalogFileSchema =
|
|
6714
|
-
skills:
|
|
8360
|
+
var SkillCatalogFileSchema = z22.object({
|
|
8361
|
+
skills: z22.array(CatalogSkillSchema)
|
|
6715
8362
|
}).strict().superRefine((file, ctx) => {
|
|
6716
8363
|
uniqueNames(file.skills, ctx);
|
|
6717
8364
|
});
|
|
6718
|
-
var GetSkillCatalogResponseSchema =
|
|
6719
|
-
data:
|
|
8365
|
+
var GetSkillCatalogResponseSchema = z22.object({
|
|
8366
|
+
data: z22.array(CatalogSkillSchema)
|
|
6720
8367
|
}).openapi("GetSkillCatalogResponse");
|
|
6721
8368
|
|
|
6722
8369
|
// src/routes/catalogRoutes.ts
|
|
6723
|
-
var CATALOG_TAG = "Catalog";
|
|
6724
8370
|
var listModelProviderCatalogRoute = createRoute5({
|
|
6725
8371
|
method: "get",
|
|
6726
8372
|
path: "/model-providers",
|
|
6727
|
-
tags: [
|
|
8373
|
+
tags: ["Models" /* MODELS */],
|
|
6728
8374
|
summary: "Get the model catalog",
|
|
6729
8375
|
description: "Shipped model-provider presets (discovery-only). Copy into PUT /settings/model-providers to configure. Includes a `custom` sentinel with `supported_reasoning_efforts`.",
|
|
6730
|
-
"x-fern-sdk-group-name": ["
|
|
8376
|
+
"x-fern-sdk-group-name": ["catalogs", "modelProviders"],
|
|
6731
8377
|
"x-fern-sdk-method-name": "list",
|
|
6732
8378
|
responses: {
|
|
6733
8379
|
200: {
|
|
@@ -6743,10 +8389,10 @@ var listModelProviderCatalogRoute = createRoute5({
|
|
|
6743
8389
|
var listMcpServerCatalogRoute = createRoute5({
|
|
6744
8390
|
method: "get",
|
|
6745
8391
|
path: "/mcp-servers",
|
|
6746
|
-
tags: [
|
|
8392
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
6747
8393
|
summary: "Get the MCP catalog",
|
|
6748
8394
|
description: "Shipped MCP server presets (discovery-only). Copy into PUT /settings/mcp-servers to configure.",
|
|
6749
|
-
"x-fern-sdk-group-name": ["
|
|
8395
|
+
"x-fern-sdk-group-name": ["catalogs", "mcpServers"],
|
|
6750
8396
|
"x-fern-sdk-method-name": "list",
|
|
6751
8397
|
responses: {
|
|
6752
8398
|
200: {
|
|
@@ -6762,10 +8408,10 @@ var listMcpServerCatalogRoute = createRoute5({
|
|
|
6762
8408
|
var listSkillCatalogRoute = createRoute5({
|
|
6763
8409
|
method: "get",
|
|
6764
8410
|
path: "/skills",
|
|
6765
|
-
tags: [
|
|
8411
|
+
tags: ["Skills" /* SKILLS */],
|
|
6766
8412
|
summary: "Get the skill catalog",
|
|
6767
8413
|
description: "Shipped skill presets (discovery-only). Copy into PUT /settings/skills to configure.",
|
|
6768
|
-
"x-fern-sdk-group-name": ["
|
|
8414
|
+
"x-fern-sdk-group-name": ["catalogs", "skills"],
|
|
6769
8415
|
"x-fern-sdk-method-name": "list",
|
|
6770
8416
|
responses: {
|
|
6771
8417
|
200: {
|
|
@@ -6781,10 +8427,10 @@ var listSkillCatalogRoute = createRoute5({
|
|
|
6781
8427
|
var listSandboxProviderCatalogRoute = createRoute5({
|
|
6782
8428
|
method: "get",
|
|
6783
8429
|
path: "/sandbox-providers",
|
|
6784
|
-
tags: [
|
|
8430
|
+
tags: ["Sandboxes" /* SANDBOXES */],
|
|
6785
8431
|
summary: "Get the sandbox provider catalog",
|
|
6786
8432
|
description: "Shipped sandbox-provider presets (discovery-only). Copy into PUT /settings/sandbox-providers to configure.",
|
|
6787
|
-
"x-fern-sdk-group-name": ["
|
|
8433
|
+
"x-fern-sdk-group-name": ["catalogs", "sandboxProviders"],
|
|
6788
8434
|
"x-fern-sdk-method-name": "list",
|
|
6789
8435
|
responses: {
|
|
6790
8436
|
200: {
|
|
@@ -6832,25 +8478,25 @@ import { extractErrorLogFields as extractErrorLogFields4, McpConnectionError as
|
|
|
6832
8478
|
|
|
6833
8479
|
// src/routes/mcpOAuthRoutes.ts
|
|
6834
8480
|
import { createRoute as createRoute6 } from "@hono/zod-openapi";
|
|
6835
|
-
var MCP_OAUTH_TAG = "MCP OAuth";
|
|
6836
8481
|
var mcpOAuthCallbackRoute = createRoute6({
|
|
6837
8482
|
method: "get",
|
|
6838
8483
|
path: "/callback",
|
|
6839
|
-
tags: [
|
|
8484
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
6840
8485
|
summary: "OAuth callback for MCP authorization",
|
|
6841
|
-
description: "Browser redirect target for MCP server OAuth. The authorization server redirects here with `code`/`state` (or `error`). Exchanges the code for tokens, then either returns JSON or redirects to the `
|
|
8486
|
+
description: "Browser redirect target for MCP server OAuth. The authorization server redirects here with `code`/`state` (or `error`). Exchanges the code for tokens, then either returns JSON or redirects to the `return_to` path supplied at authorize time. Not called by the SDK \u2014 browsers hit this URL directly.",
|
|
6842
8487
|
// Browser-redirect target hit directly by the authorization server, never called by SDK.
|
|
6843
8488
|
"x-fern-ignore": true,
|
|
8489
|
+
"x-excluded": true,
|
|
6844
8490
|
request: {
|
|
6845
8491
|
query: OAuthCallbackQuerySchema
|
|
6846
8492
|
},
|
|
6847
8493
|
responses: {
|
|
6848
8494
|
200: {
|
|
6849
8495
|
content: { "application/json": { schema: OAuthCallbackSuccessSchema } },
|
|
6850
|
-
description: "Token exchanged successfully and no `
|
|
8496
|
+
description: "Token exchanged successfully and no `return_to` was given at authorize time."
|
|
6851
8497
|
},
|
|
6852
8498
|
302: {
|
|
6853
|
-
description: "Redirect to the `
|
|
8499
|
+
description: "Redirect to the `return_to` path given at authorize time, with `isSuccess` (and `reason` when it failed) appended to its existing query params."
|
|
6854
8500
|
},
|
|
6855
8501
|
400: {
|
|
6856
8502
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
@@ -6864,25 +8510,28 @@ var mcpOAuthCallbackRoute = createRoute6({
|
|
|
6864
8510
|
});
|
|
6865
8511
|
|
|
6866
8512
|
// src/apis/mcpOAuth.ts
|
|
6867
|
-
function
|
|
6868
|
-
const url = new URL(params.
|
|
8513
|
+
function callbackLandingPath(params) {
|
|
8514
|
+
const url = new URL(params.returnTo, params.c.req.url);
|
|
6869
8515
|
url.searchParams.set("isSuccess", String(params.isSuccess));
|
|
6870
8516
|
if (params.reason) {
|
|
6871
8517
|
url.searchParams.set("reason", params.reason);
|
|
6872
8518
|
}
|
|
6873
|
-
return url.
|
|
8519
|
+
return `${url.pathname}${url.search}`;
|
|
6874
8520
|
}
|
|
6875
8521
|
function callbackSuccess(params) {
|
|
6876
|
-
const
|
|
6877
|
-
if (
|
|
6878
|
-
return params.c.redirect(
|
|
8522
|
+
const returnTo = params.pending.returnTo;
|
|
8523
|
+
if (returnTo) {
|
|
8524
|
+
return params.c.redirect(callbackLandingPath({ c: params.c, returnTo, isSuccess: true }), 302);
|
|
6879
8525
|
}
|
|
6880
8526
|
return params.c.json({ success: true }, 200);
|
|
6881
8527
|
}
|
|
6882
8528
|
function callbackFailure(params) {
|
|
6883
|
-
const
|
|
6884
|
-
if (
|
|
6885
|
-
return params.c.redirect(
|
|
8529
|
+
const returnTo = params.pending?.returnTo;
|
|
8530
|
+
if (returnTo) {
|
|
8531
|
+
return params.c.redirect(
|
|
8532
|
+
callbackLandingPath({ c: params.c, returnTo, isSuccess: false, reason: params.message }),
|
|
8533
|
+
302
|
|
8534
|
+
);
|
|
6886
8535
|
}
|
|
6887
8536
|
return params.c.json({ error: { message: params.message } }, params.jsonStatus ?? 400);
|
|
6888
8537
|
}
|
|
@@ -6924,18 +8573,17 @@ function createMcpOAuthRouter(deps) {
|
|
|
6924
8573
|
}
|
|
6925
8574
|
|
|
6926
8575
|
// src/apis/mcpServers.ts
|
|
6927
|
-
init_config();
|
|
6928
|
-
init_mcpServerStore();
|
|
6929
8576
|
import { OpenAPIHono as OpenAPIHono7 } from "@hono/zod-openapi";
|
|
6930
8577
|
import { extractErrorLogFields as extractErrorLogFields5, isAuthRequired, McpConnectionError as McpConnectionError4, RemoteMCP } from "@truefoundry/trueforge-core/core";
|
|
8578
|
+
init_config();
|
|
8579
|
+
init_mcpServerStore();
|
|
6931
8580
|
|
|
6932
8581
|
// src/routes/mcpServerRoutes.ts
|
|
6933
|
-
import { createRoute as createRoute7, z as
|
|
6934
|
-
var MCP_SERVERS_TAG = "MCP Servers";
|
|
8582
|
+
import { createRoute as createRoute7, z as z23 } from "@hono/zod-openapi";
|
|
6935
8583
|
var listAvailableMcpServersRoute = createRoute7({
|
|
6936
8584
|
method: "get",
|
|
6937
8585
|
path: "/",
|
|
6938
|
-
tags: [
|
|
8586
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
6939
8587
|
summary: "List MCP servers for chat",
|
|
6940
8588
|
description: "MCP servers as a slim name/url list for the composer. No auth or auth_status.",
|
|
6941
8589
|
"x-fern-sdk-group-name": ["mcpServers"],
|
|
@@ -6954,7 +8602,7 @@ var listAvailableMcpServersRoute = createRoute7({
|
|
|
6954
8602
|
var listMcpServersRoute = createRoute7({
|
|
6955
8603
|
method: "get",
|
|
6956
8604
|
path: "/",
|
|
6957
|
-
tags: [
|
|
8605
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
6958
8606
|
summary: "List MCP servers",
|
|
6959
8607
|
description: "All MCP servers with nested auth_status (settings / admin projection). Header auth values are redacted.",
|
|
6960
8608
|
"x-fern-sdk-group-name": ["settings", "mcpServers"],
|
|
@@ -6974,13 +8622,13 @@ var listMcpServersRoute = createRoute7({
|
|
|
6974
8622
|
}
|
|
6975
8623
|
}
|
|
6976
8624
|
});
|
|
6977
|
-
var McpServerNameParamsSchema =
|
|
6978
|
-
name:
|
|
8625
|
+
var McpServerNameParamsSchema = z23.object({
|
|
8626
|
+
name: z23.string().min(1).describe("MCP server name.")
|
|
6979
8627
|
});
|
|
6980
8628
|
var getMcpServerRoute = createRoute7({
|
|
6981
8629
|
method: "get",
|
|
6982
8630
|
path: "/{name}",
|
|
6983
|
-
tags: [
|
|
8631
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
6984
8632
|
summary: "Get a single MCP server by name",
|
|
6985
8633
|
description: "A single MCP server by name, with nested auth_status (settings / admin projection). Header auth values are redacted.",
|
|
6986
8634
|
"x-fern-sdk-group-name": ["settings", "mcpServers"],
|
|
@@ -7002,7 +8650,7 @@ var getMcpServerRoute = createRoute7({
|
|
|
7002
8650
|
var createMcpServerRoute = createRoute7({
|
|
7003
8651
|
method: "post",
|
|
7004
8652
|
path: "/",
|
|
7005
|
-
tags: [
|
|
8653
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
7006
8654
|
summary: "Create an MCP server",
|
|
7007
8655
|
description: "Creates an MCP server by `name`. Fails if `name` is already taken. Runs DCR registration when `auth.type` is `dcr`. Header secrets: real value required; redacted with no stored value returns 400.",
|
|
7008
8656
|
"x-fern-sdk-group-name": ["settings", "mcpServers"],
|
|
@@ -7035,14 +8683,14 @@ var createMcpServerRoute = createRoute7({
|
|
|
7035
8683
|
var putMcpServerRoute = createRoute7({
|
|
7036
8684
|
method: "put",
|
|
7037
8685
|
path: "/",
|
|
7038
|
-
tags: [
|
|
8686
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
7039
8687
|
summary: "Create or replace an MCP server",
|
|
7040
8688
|
description: "Create or replace by `name`. Does not start DCR or change oauth client columns. Header secrets: real value sets/rotates; redacted keeps existing (400 if none).",
|
|
7041
8689
|
"x-fern-sdk-group-name": ["settings", "mcpServers"],
|
|
7042
|
-
"x-fern-sdk-method-name": "
|
|
8690
|
+
"x-fern-sdk-method-name": "create_or_update",
|
|
7043
8691
|
request: {
|
|
7044
8692
|
body: {
|
|
7045
|
-
content: { "application/json": { schema:
|
|
8693
|
+
content: { "application/json": { schema: UpdateMcpServerRequestSchema } },
|
|
7046
8694
|
required: true
|
|
7047
8695
|
}
|
|
7048
8696
|
},
|
|
@@ -7061,16 +8709,16 @@ var putMcpServerRoute = createRoute7({
|
|
|
7061
8709
|
}
|
|
7062
8710
|
}
|
|
7063
8711
|
});
|
|
7064
|
-
var ListMcpServerToolsResponseSchema =
|
|
8712
|
+
var ListMcpServerToolsResponseSchema = z23.object({
|
|
7065
8713
|
// TODO: Type tools/list entries to the MCP tool shape (name, description, inputSchema, …) for OpenAPI quality.
|
|
7066
|
-
data:
|
|
7067
|
-
}).openapi("
|
|
8714
|
+
data: z23.array(z23.record(z23.string(), z23.unknown())).describe("MCP `tools/list` entries, passed through verbatim from the MCP server.")
|
|
8715
|
+
}).openapi("ListMCPServerToolsResponse");
|
|
7068
8716
|
var listMcpServerToolsRoute = createRoute7({
|
|
7069
8717
|
method: "get",
|
|
7070
8718
|
path: "/{name}/tools",
|
|
7071
|
-
tags: [
|
|
8719
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
7072
8720
|
summary: "List tools of an MCP server",
|
|
7073
|
-
"x-fern-sdk-group-name": ["
|
|
8721
|
+
"x-fern-sdk-group-name": ["mcpServers"],
|
|
7074
8722
|
"x-fern-sdk-method-name": "list_tools",
|
|
7075
8723
|
description: "All tools exposed by the given MCP server (non-paginated), as returned by the MCP `tools/list` call.",
|
|
7076
8724
|
request: {
|
|
@@ -7083,7 +8731,7 @@ var listMcpServerToolsRoute = createRoute7({
|
|
|
7083
8731
|
},
|
|
7084
8732
|
401: {
|
|
7085
8733
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
7086
|
-
description: "
|
|
8734
|
+
description: "OIDC is configured and the request has no valid session cookie."
|
|
7087
8735
|
},
|
|
7088
8736
|
404: {
|
|
7089
8737
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
@@ -7099,17 +8747,19 @@ var listMcpServerToolsRoute = createRoute7({
|
|
|
7099
8747
|
}
|
|
7100
8748
|
}
|
|
7101
8749
|
});
|
|
7102
|
-
var McpAuthorizeQuerySchema =
|
|
7103
|
-
|
|
8750
|
+
var McpAuthorizeQuerySchema = z23.object({
|
|
8751
|
+
return_to: z23.string().optional().describe(
|
|
8752
|
+
"Optional path to return to after OAuth. Must be a same-origin relative path; the OAuth callback redirects here with `isSuccess`/`reason` appended."
|
|
8753
|
+
)
|
|
7104
8754
|
});
|
|
7105
8755
|
var authorizeMcpServerRoute = createRoute7({
|
|
7106
8756
|
method: "get",
|
|
7107
8757
|
path: "/{name}/authorize",
|
|
7108
|
-
tags: [
|
|
8758
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
7109
8759
|
summary: "Start (or short-circuit) the auth flow for an MCP server",
|
|
7110
8760
|
"x-fern-sdk-group-name": ["mcpServers"],
|
|
7111
8761
|
"x-fern-sdk-method-name": "authorize",
|
|
7112
|
-
description: "For servers without auth returns not_required, and for header credentials returns authenticated (no browser flow). For auth.type dcr, returns authenticated when a usable (or refreshable) token exists; otherwise runs DCR if needed and returns auth_required with an authorization URL. Optional
|
|
8762
|
+
description: "For servers without auth returns not_required, and for header credentials returns authenticated (no browser flow). For auth.type dcr, returns authenticated when a usable (or refreshable) token exists; otherwise runs DCR if needed and returns auth_required with an authorization URL. Optional return_to is where the OAuth callback then redirects the browser; without it the callback returns JSON.",
|
|
7113
8763
|
request: {
|
|
7114
8764
|
params: McpServerNameParamsSchema,
|
|
7115
8765
|
query: McpAuthorizeQuerySchema
|
|
@@ -7121,7 +8771,7 @@ var authorizeMcpServerRoute = createRoute7({
|
|
|
7121
8771
|
},
|
|
7122
8772
|
400: {
|
|
7123
8773
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
7124
|
-
description: "Invalid
|
|
8774
|
+
description: "Invalid return_to."
|
|
7125
8775
|
},
|
|
7126
8776
|
404: {
|
|
7127
8777
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
@@ -7141,13 +8791,13 @@ var authorizeMcpServerRoute = createRoute7({
|
|
|
7141
8791
|
}
|
|
7142
8792
|
}
|
|
7143
8793
|
});
|
|
7144
|
-
var
|
|
8794
|
+
var deleteAuthorizationMcpServerRoute = createRoute7({
|
|
7145
8795
|
method: "delete",
|
|
7146
8796
|
path: "/{name}/authorize",
|
|
7147
|
-
tags: [
|
|
8797
|
+
tags: ["MCP Servers" /* MCP_SERVERS */],
|
|
7148
8798
|
summary: "Disconnect OAuth for an MCP server",
|
|
7149
8799
|
"x-fern-sdk-group-name": ["mcpServers"],
|
|
7150
|
-
"x-fern-sdk-method-name": "
|
|
8800
|
+
"x-fern-sdk-method-name": "delete_authorization",
|
|
7151
8801
|
description: "For auth.type dcr, deletes the stored OAuth token and returns the server with auth_status auth_required, keeping the dynamically registered OAuth client so the next authorize can reuse it. No-op for header or no-auth servers (returns the server unchanged).",
|
|
7152
8802
|
request: {
|
|
7153
8803
|
params: McpServerNameParamsSchema
|
|
@@ -7251,7 +8901,7 @@ function toConfiguredMcpServer({
|
|
|
7251
8901
|
})
|
|
7252
8902
|
};
|
|
7253
8903
|
}
|
|
7254
|
-
function
|
|
8904
|
+
function toAvailableMcpServer({
|
|
7255
8905
|
record,
|
|
7256
8906
|
token
|
|
7257
8907
|
}) {
|
|
@@ -7407,60 +9057,17 @@ function createSettingsMcpServersRouter(deps) {
|
|
|
7407
9057
|
throw error;
|
|
7408
9058
|
}
|
|
7409
9059
|
};
|
|
7410
|
-
const listToolsHandler = async (c) => {
|
|
7411
|
-
const { name } = c.req.valid("param");
|
|
7412
|
-
const userRef = deps.resolveUserContext(c).userRef;
|
|
7413
|
-
const connection = await getMcpConnection({
|
|
7414
|
-
tenant_id: TENANT_ID,
|
|
7415
|
-
name,
|
|
7416
|
-
store: deps.mcpServerStore,
|
|
7417
|
-
tokenStore: deps.tokenStore,
|
|
7418
|
-
clientName: config_default.MCP_DCR_OAUTH_CLIENT_NAME,
|
|
7419
|
-
userRef
|
|
7420
|
-
});
|
|
7421
|
-
if (connection === void 0) {
|
|
7422
|
-
return c.json({ error: { message: `MCP server not found: ${name}` } }, 404);
|
|
7423
|
-
}
|
|
7424
|
-
const remote = new RemoteMCP({
|
|
7425
|
-
id: name,
|
|
7426
|
-
name,
|
|
7427
|
-
url: connection.url,
|
|
7428
|
-
headers: connection.headers,
|
|
7429
|
-
requestTimeoutMs: config_default.MCP_REQUEST_TIMEOUT_MS,
|
|
7430
|
-
connectTimeoutMs: config_default.MCP_CONNECT_TIMEOUT_MS,
|
|
7431
|
-
logger: deps.logger,
|
|
7432
|
-
signal: c.req.raw.signal
|
|
7433
|
-
});
|
|
7434
|
-
try {
|
|
7435
|
-
const response = await remote.listTools();
|
|
7436
|
-
if (isAuthRequired(response)) {
|
|
7437
|
-
return c.json({ error: { message: `MCP server "${name}" requires authentication` } }, 422);
|
|
7438
|
-
}
|
|
7439
|
-
const data = response.result.tools.map((tool) => omitUndefinedEntries({ ...tool }));
|
|
7440
|
-
return c.json({ data }, 200);
|
|
7441
|
-
} catch (error) {
|
|
7442
|
-
if (error instanceof McpConnectionError4) {
|
|
7443
|
-
deps.logger.warn(`MCP tools/list failed for "${name}"`, extractErrorLogFields5(error));
|
|
7444
|
-
if (error.statusCode === 401) {
|
|
7445
|
-
return c.json({ error: { message: error.message } }, 422);
|
|
7446
|
-
}
|
|
7447
|
-
return c.json({ error: { message: error.message } }, 502);
|
|
7448
|
-
}
|
|
7449
|
-
throw error;
|
|
7450
|
-
}
|
|
7451
|
-
};
|
|
7452
9060
|
const router = new OpenAPIHono7();
|
|
7453
9061
|
router.openapi(listMcpServersRoute, listHandler);
|
|
7454
9062
|
router.openapi(createMcpServerRoute, createHandler);
|
|
7455
9063
|
router.openapi(putMcpServerRoute, putHandler);
|
|
7456
|
-
router.openapi(listMcpServerToolsRoute, listToolsHandler);
|
|
7457
9064
|
router.openapi(getMcpServerRoute, getHandler);
|
|
7458
9065
|
return router;
|
|
7459
9066
|
}
|
|
7460
9067
|
function createMcpServersRouter(deps) {
|
|
7461
9068
|
const authorizeHandler = async (c) => {
|
|
7462
9069
|
const { name } = c.req.valid("param");
|
|
7463
|
-
const {
|
|
9070
|
+
const { return_to: returnTo } = c.req.valid("query");
|
|
7464
9071
|
const userRef = deps.resolveUserContext(c).userRef;
|
|
7465
9072
|
const record = await deps.mcpServerStore.getServer({ tenant_id: TENANT_ID, name });
|
|
7466
9073
|
if (!record) {
|
|
@@ -7469,10 +9076,10 @@ function createMcpServersRouter(deps) {
|
|
|
7469
9076
|
if (record.manifest.auth?.type !== "dcr") {
|
|
7470
9077
|
return c.json(resolveMcpAuthStatus({ manifest: record.manifest }), 200);
|
|
7471
9078
|
}
|
|
9079
|
+
if (returnTo && safeReturnTo(returnTo) !== returnTo) {
|
|
9080
|
+
return c.json({ error: { message: "Invalid return_to: must be a same-origin relative path" } }, 400);
|
|
9081
|
+
}
|
|
7472
9082
|
try {
|
|
7473
|
-
if (redirectUrl) {
|
|
7474
|
-
validateRedirectUris({ redirectUris: [redirectUrl] });
|
|
7475
|
-
}
|
|
7476
9083
|
const result = await resolveMcpAuth({
|
|
7477
9084
|
tokenStore: deps.tokenStore,
|
|
7478
9085
|
mcpServerStore: deps.mcpServerStore,
|
|
@@ -7481,7 +9088,7 @@ function createMcpServersRouter(deps) {
|
|
|
7481
9088
|
mcpServerUrl: record.manifest.url,
|
|
7482
9089
|
mcpServerName: record.name,
|
|
7483
9090
|
clientName: config_default.MCP_DCR_OAUTH_CLIENT_NAME,
|
|
7484
|
-
...
|
|
9091
|
+
...returnTo !== void 0 ? { returnTo } : {}
|
|
7485
9092
|
});
|
|
7486
9093
|
const authStatus = isMcpAuthRequired(result) ? { status: "auth_required", authorization_url: result.authUrl.href } : { status: "authenticated" };
|
|
7487
9094
|
return c.json(authStatus, 200);
|
|
@@ -7503,7 +9110,49 @@ function createMcpServersRouter(deps) {
|
|
|
7503
9110
|
return c.json({ error: { message: "Internal server error" } }, 500);
|
|
7504
9111
|
}
|
|
7505
9112
|
};
|
|
7506
|
-
const
|
|
9113
|
+
const listToolsHandler = async (c) => {
|
|
9114
|
+
const { name } = c.req.valid("param");
|
|
9115
|
+
const userRef = deps.resolveUserContext(c).userRef;
|
|
9116
|
+
const connection = await getMcpConnection({
|
|
9117
|
+
tenant_id: TENANT_ID,
|
|
9118
|
+
name,
|
|
9119
|
+
store: deps.mcpServerStore,
|
|
9120
|
+
tokenStore: deps.tokenStore,
|
|
9121
|
+
clientName: config_default.MCP_DCR_OAUTH_CLIENT_NAME,
|
|
9122
|
+
userRef
|
|
9123
|
+
});
|
|
9124
|
+
if (connection === void 0) {
|
|
9125
|
+
return c.json({ error: { message: `MCP server not found: ${name}` } }, 404);
|
|
9126
|
+
}
|
|
9127
|
+
const remote = new RemoteMCP({
|
|
9128
|
+
id: name,
|
|
9129
|
+
name,
|
|
9130
|
+
url: connection.url,
|
|
9131
|
+
headers: connection.headers,
|
|
9132
|
+
requestTimeoutMs: config_default.MCP_REQUEST_TIMEOUT_MS,
|
|
9133
|
+
connectTimeoutMs: config_default.MCP_CONNECT_TIMEOUT_MS,
|
|
9134
|
+
logger: deps.logger,
|
|
9135
|
+
signal: c.req.raw.signal
|
|
9136
|
+
});
|
|
9137
|
+
try {
|
|
9138
|
+
const response = await remote.listTools();
|
|
9139
|
+
if (isAuthRequired(response)) {
|
|
9140
|
+
return c.json({ error: { message: `MCP server "${name}" requires authentication` } }, 422);
|
|
9141
|
+
}
|
|
9142
|
+
const data = response.result.tools.map((tool) => omitUndefinedEntries({ ...tool }));
|
|
9143
|
+
return c.json({ data }, 200);
|
|
9144
|
+
} catch (error) {
|
|
9145
|
+
if (error instanceof McpConnectionError4) {
|
|
9146
|
+
deps.logger.warn(`MCP tools/list failed for "${name}"`, extractErrorLogFields5(error));
|
|
9147
|
+
if (error.statusCode === 401) {
|
|
9148
|
+
return c.json({ error: { message: error.message } }, 422);
|
|
9149
|
+
}
|
|
9150
|
+
return c.json({ error: { message: error.message } }, 502);
|
|
9151
|
+
}
|
|
9152
|
+
throw error;
|
|
9153
|
+
}
|
|
9154
|
+
};
|
|
9155
|
+
const deleteAuthorizationHandler = async (c) => {
|
|
7507
9156
|
const { name } = c.req.valid("param");
|
|
7508
9157
|
const userRef = deps.resolveUserContext(c).userRef;
|
|
7509
9158
|
const record = await deps.mcpServerStore.getServer({ tenant_id: TENANT_ID, name });
|
|
@@ -7523,13 +9172,14 @@ function createMcpServersRouter(deps) {
|
|
|
7523
9172
|
const tokens = await deps.tokenStore.getTokens({ ids: dcrIds, userRef });
|
|
7524
9173
|
return c.json(
|
|
7525
9174
|
{
|
|
7526
|
-
data: records.map((record) =>
|
|
9175
|
+
data: records.map((record) => toAvailableMcpServer({ record, token: tokens.get(record.id) }))
|
|
7527
9176
|
},
|
|
7528
9177
|
200
|
|
7529
9178
|
);
|
|
7530
9179
|
});
|
|
9180
|
+
router.openapi(listMcpServerToolsRoute, listToolsHandler);
|
|
7531
9181
|
router.openapi(authorizeMcpServerRoute, authorizeHandler);
|
|
7532
|
-
router.openapi(
|
|
9182
|
+
router.openapi(deleteAuthorizationMcpServerRoute, deleteAuthorizationHandler);
|
|
7533
9183
|
return router;
|
|
7534
9184
|
}
|
|
7535
9185
|
|
|
@@ -7538,18 +9188,18 @@ import { OpenAPIHono as OpenAPIHono8 } from "@hono/zod-openapi";
|
|
|
7538
9188
|
|
|
7539
9189
|
// src/routes/modelRoutes.ts
|
|
7540
9190
|
import { createRoute as createRoute8 } from "@hono/zod-openapi";
|
|
7541
|
-
var
|
|
9191
|
+
var listAvailableModelsRoute = createRoute8({
|
|
7542
9192
|
method: "get",
|
|
7543
9193
|
path: "/",
|
|
7544
|
-
tags: ["Models"],
|
|
7545
|
-
summary: "List models",
|
|
9194
|
+
tags: ["Models" /* MODELS */],
|
|
9195
|
+
summary: "List models for chat",
|
|
7546
9196
|
"x-fern-sdk-group-name": ["models"],
|
|
7547
9197
|
"x-fern-sdk-method-name": "list",
|
|
7548
|
-
description: "
|
|
9198
|
+
description: "Configured models as a slim FQN list for the composer.",
|
|
7549
9199
|
responses: {
|
|
7550
9200
|
200: {
|
|
7551
|
-
content: { "application/json": { schema:
|
|
7552
|
-
description: "All models
|
|
9201
|
+
content: { "application/json": { schema: ListAvailableModelsResponseSchema } },
|
|
9202
|
+
description: "All configured models (chat projection)."
|
|
7553
9203
|
},
|
|
7554
9204
|
401: {
|
|
7555
9205
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
@@ -7562,7 +9212,7 @@ var listModelsRoute = createRoute8({
|
|
|
7562
9212
|
function createModelsRouter(deps) {
|
|
7563
9213
|
const router = new OpenAPIHono8();
|
|
7564
9214
|
router.openapi(
|
|
7565
|
-
|
|
9215
|
+
listAvailableModelsRoute,
|
|
7566
9216
|
async (c) => c.json({ data: await deps.modelProviderStore.listModels(TENANT_ID) }, 200)
|
|
7567
9217
|
);
|
|
7568
9218
|
return router;
|
|
@@ -7577,11 +9227,10 @@ import { OpenAPIHono as OpenAPIHono9 } from "@hono/zod-openapi";
|
|
|
7577
9227
|
|
|
7578
9228
|
// src/routes/modelProviderRoutes.ts
|
|
7579
9229
|
import { createRoute as createRoute9 } from "@hono/zod-openapi";
|
|
7580
|
-
var MODEL_PROVIDERS_TAG = "Model Providers";
|
|
7581
9230
|
var listModelProvidersRoute = createRoute9({
|
|
7582
9231
|
method: "get",
|
|
7583
9232
|
path: "/",
|
|
7584
|
-
tags: [
|
|
9233
|
+
tags: ["Models" /* MODELS */],
|
|
7585
9234
|
summary: "List configured model providers",
|
|
7586
9235
|
description: "All configured providers with nested manifests.",
|
|
7587
9236
|
"x-fern-sdk-group-name": ["settings", "modelProviders"],
|
|
@@ -7604,7 +9253,7 @@ var listModelProvidersRoute = createRoute9({
|
|
|
7604
9253
|
var createModelProviderRoute = createRoute9({
|
|
7605
9254
|
method: "post",
|
|
7606
9255
|
path: "/",
|
|
7607
|
-
tags: [
|
|
9256
|
+
tags: ["Models" /* MODELS */],
|
|
7608
9257
|
summary: "Create a model provider",
|
|
7609
9258
|
description: "Creates a provider (models included). Fails if `name` is already taken. Well-known types use `type` as `name` (one each); `custom` is named by the caller. `auth.api_key`: real value required; redacted with no stored secret returns 400.",
|
|
7610
9259
|
"x-fern-sdk-group-name": ["settings", "modelProviders"],
|
|
@@ -7633,14 +9282,14 @@ var createModelProviderRoute = createRoute9({
|
|
|
7633
9282
|
var putModelProviderRoute = createRoute9({
|
|
7634
9283
|
method: "put",
|
|
7635
9284
|
path: "/",
|
|
7636
|
-
tags: [
|
|
9285
|
+
tags: ["Models" /* MODELS */],
|
|
7637
9286
|
summary: "Create or replace a model provider",
|
|
7638
9287
|
description: "Create or replace a provider (models included). Well-known types use `type` as `name` (one each); `custom` is named by the caller. `auth.api_key`: real value sets/rotates; redacted keeps existing (400 if none).",
|
|
7639
9288
|
"x-fern-sdk-group-name": ["settings", "modelProviders"],
|
|
7640
|
-
"x-fern-sdk-method-name": "
|
|
9289
|
+
"x-fern-sdk-method-name": "create_or_update",
|
|
7641
9290
|
request: {
|
|
7642
9291
|
body: {
|
|
7643
|
-
content: { "application/json": { schema:
|
|
9292
|
+
content: { "application/json": { schema: UpdateModelProviderRequestSchema } },
|
|
7644
9293
|
required: true
|
|
7645
9294
|
}
|
|
7646
9295
|
},
|
|
@@ -7749,11 +9398,10 @@ import { withTimeout } from "@truefoundry/trueforge-core/core";
|
|
|
7749
9398
|
|
|
7750
9399
|
// src/routes/sandboxProviderRoutes.ts
|
|
7751
9400
|
import { createRoute as createRoute10 } from "@hono/zod-openapi";
|
|
7752
|
-
var SANDBOX_PROVIDERS_TAG = "Sandbox Providers";
|
|
7753
9401
|
var getSandboxProviderRoute = createRoute10({
|
|
7754
9402
|
method: "get",
|
|
7755
9403
|
path: "/",
|
|
7756
|
-
tags: [
|
|
9404
|
+
tags: ["Sandboxes" /* SANDBOXES */],
|
|
7757
9405
|
summary: "Get the configured sandbox provider",
|
|
7758
9406
|
description: "The single configured sandbox provider for this tenant. `auth.api_key` is redacted.",
|
|
7759
9407
|
"x-fern-sdk-group-name": ["settings", "sandboxProviders"],
|
|
@@ -7772,14 +9420,14 @@ var getSandboxProviderRoute = createRoute10({
|
|
|
7772
9420
|
var putSandboxProviderRoute = createRoute10({
|
|
7773
9421
|
method: "put",
|
|
7774
9422
|
path: "/",
|
|
7775
|
-
tags: [
|
|
9423
|
+
tags: ["Sandboxes" /* SANDBOXES */],
|
|
7776
9424
|
summary: "Create or replace the sandbox provider",
|
|
7777
9425
|
description: "Upserts the single sandbox provider for this tenant: creates it or replaces its entire configuration. `auth.api_key`: real value sets/rotates; redacted keeps existing (400 if none).",
|
|
7778
9426
|
"x-fern-sdk-group-name": ["settings", "sandboxProviders"],
|
|
7779
|
-
"x-fern-sdk-method-name": "
|
|
9427
|
+
"x-fern-sdk-method-name": "create_or_update",
|
|
7780
9428
|
request: {
|
|
7781
9429
|
body: {
|
|
7782
|
-
content: { "application/json": { schema:
|
|
9430
|
+
content: { "application/json": { schema: UpdateSandboxProviderRequestSchema } },
|
|
7783
9431
|
required: true
|
|
7784
9432
|
}
|
|
7785
9433
|
},
|
|
@@ -7892,11 +9540,10 @@ import { OpenAPIHono as OpenAPIHono11 } from "@hono/zod-openapi";
|
|
|
7892
9540
|
|
|
7893
9541
|
// src/routes/skillRoutes.ts
|
|
7894
9542
|
import { createRoute as createRoute11 } from "@hono/zod-openapi";
|
|
7895
|
-
var SKILLS_TAG = "Skills";
|
|
7896
9543
|
var listAvailableSkillsRoute = createRoute11({
|
|
7897
9544
|
method: "get",
|
|
7898
9545
|
path: "/",
|
|
7899
|
-
tags: [
|
|
9546
|
+
tags: ["Skills" /* SKILLS */],
|
|
7900
9547
|
summary: "List skills for chat",
|
|
7901
9548
|
description: "Configured skills as a slim name/description list for the composer.",
|
|
7902
9549
|
"x-fern-sdk-group-name": ["skills"],
|
|
@@ -7915,7 +9562,7 @@ var listAvailableSkillsRoute = createRoute11({
|
|
|
7915
9562
|
var listConfiguredSkillsRoute = createRoute11({
|
|
7916
9563
|
method: "get",
|
|
7917
9564
|
path: "/",
|
|
7918
|
-
tags: [
|
|
9565
|
+
tags: ["Skills" /* SKILLS */],
|
|
7919
9566
|
summary: "List configured skills",
|
|
7920
9567
|
description: "All configured skills with nested manifests (settings / admin projection).",
|
|
7921
9568
|
"x-fern-sdk-group-name": ["settings", "skills"],
|
|
@@ -7938,7 +9585,7 @@ var listConfiguredSkillsRoute = createRoute11({
|
|
|
7938
9585
|
var createSkillRoute = createRoute11({
|
|
7939
9586
|
method: "post",
|
|
7940
9587
|
path: "/",
|
|
7941
|
-
tags: [
|
|
9588
|
+
tags: ["Skills" /* SKILLS */],
|
|
7942
9589
|
summary: "Create a skill",
|
|
7943
9590
|
description: "Creates a skill keyed by `name`. Fails if `name` is already taken.",
|
|
7944
9591
|
"x-fern-sdk-group-name": ["settings", "skills"],
|
|
@@ -7967,14 +9614,14 @@ var createSkillRoute = createRoute11({
|
|
|
7967
9614
|
var putSkillRoute = createRoute11({
|
|
7968
9615
|
method: "put",
|
|
7969
9616
|
path: "/",
|
|
7970
|
-
tags: [
|
|
9617
|
+
tags: ["Skills" /* SKILLS */],
|
|
7971
9618
|
summary: "Create or replace a skill",
|
|
7972
9619
|
description: "Full upsert keyed by `name`: creates the skill or replaces its entire manifest.",
|
|
7973
9620
|
"x-fern-sdk-group-name": ["settings", "skills"],
|
|
7974
|
-
"x-fern-sdk-method-name": "
|
|
9621
|
+
"x-fern-sdk-method-name": "create_or_update",
|
|
7975
9622
|
request: {
|
|
7976
9623
|
body: {
|
|
7977
|
-
content: { "application/json": { schema:
|
|
9624
|
+
content: { "application/json": { schema: UpdateSkillRequestSchema } },
|
|
7978
9625
|
required: true
|
|
7979
9626
|
}
|
|
7980
9627
|
},
|
|
@@ -8102,27 +9749,27 @@ import {
|
|
|
8102
9749
|
} from "@truefoundry/trueforge-core/agent-session";
|
|
8103
9750
|
import {
|
|
8104
9751
|
AgentHarnessError,
|
|
9752
|
+
existingSandboxIdForProvider,
|
|
8105
9753
|
extractErrorLogFields as extractErrorLogFields6,
|
|
8106
9754
|
isAgentInputUserMessage,
|
|
8107
9755
|
isFileContentPart,
|
|
8108
9756
|
McpConnectionError as McpConnectionError5,
|
|
9757
|
+
rawSandboxId,
|
|
8109
9758
|
SandboxError as SandboxError2,
|
|
8110
|
-
validateSandboxOwnedByTenant,
|
|
8111
9759
|
VercelAILLM
|
|
8112
9760
|
} from "@truefoundry/trueforge-core/core";
|
|
8113
9761
|
import { HTTPException as HTTPException4 } from "hono/http-exception";
|
|
8114
9762
|
import { streamSSE } from "hono/streaming";
|
|
8115
9763
|
|
|
8116
9764
|
// src/routes/turnRoutes.ts
|
|
8117
|
-
import { createRoute as createRoute12, z as
|
|
8118
|
-
var SESSIONS_TAG2 = "Sessions";
|
|
9765
|
+
import { createRoute as createRoute12, z as z24 } from "@hono/zod-openapi";
|
|
8119
9766
|
var TurnIdParamsSchema = SessionIdParamsSchema.extend({
|
|
8120
|
-
turn_id:
|
|
9767
|
+
turn_id: z24.string().min(1).describe("Turn identifier.")
|
|
8121
9768
|
});
|
|
8122
9769
|
var listTurnsRoute = createRoute12({
|
|
8123
9770
|
method: "get",
|
|
8124
9771
|
path: "/{session_id}/turns",
|
|
8125
|
-
tags: [
|
|
9772
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8126
9773
|
summary: "List turns in a session",
|
|
8127
9774
|
description: "List turns for a session (newest first by default), token-paginated. Only the session creator (`created_by`) may list turns.",
|
|
8128
9775
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -8154,7 +9801,7 @@ var listTurnsRoute = createRoute12({
|
|
|
8154
9801
|
var getTurnRoute = createRoute12({
|
|
8155
9802
|
method: "get",
|
|
8156
9803
|
path: "/{session_id}/turns/{turn_id}",
|
|
8157
|
-
tags: [
|
|
9804
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8158
9805
|
summary: "Get a turn",
|
|
8159
9806
|
description: "Fetch a single turn by ID. Only the session creator (`created_by`) may fetch it.",
|
|
8160
9807
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -8179,8 +9826,8 @@ var getTurnRoute = createRoute12({
|
|
|
8179
9826
|
});
|
|
8180
9827
|
var downloadSandboxFileRoute = createRoute12({
|
|
8181
9828
|
method: "get",
|
|
8182
|
-
path: "/{session_id}/turns/{turn_id}/download",
|
|
8183
|
-
tags: [
|
|
9829
|
+
path: "/{session_id}/turns/{turn_id}/download-sandbox-file",
|
|
9830
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8184
9831
|
summary: "Download a file from the turn sandbox",
|
|
8185
9832
|
description: "Download a file from the sandbox this turn ran in. Paths come from the assistant's `sandbox_artifacts` block. Only the session creator (`created_by`) may download.",
|
|
8186
9833
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -8191,7 +9838,7 @@ var downloadSandboxFileRoute = createRoute12({
|
|
|
8191
9838
|
},
|
|
8192
9839
|
responses: {
|
|
8193
9840
|
200: {
|
|
8194
|
-
content: { "application/octet-stream": { schema:
|
|
9841
|
+
content: { "application/octet-stream": { schema: z24.string().openapi({ format: "binary" }) } },
|
|
8195
9842
|
description: "File contents."
|
|
8196
9843
|
},
|
|
8197
9844
|
400: {
|
|
@@ -8200,7 +9847,7 @@ var downloadSandboxFileRoute = createRoute12({
|
|
|
8200
9847
|
},
|
|
8201
9848
|
403: {
|
|
8202
9849
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
8203
|
-
description: "Caller is not the session creator
|
|
9850
|
+
description: "Caller is not the session creator."
|
|
8204
9851
|
},
|
|
8205
9852
|
404: {
|
|
8206
9853
|
content: { "application/json": { schema: RequestErrorResponseSchema } },
|
|
@@ -8227,7 +9874,7 @@ var downloadSandboxFileRoute = createRoute12({
|
|
|
8227
9874
|
var listTurnEventsRoute = createRoute12({
|
|
8228
9875
|
method: "get",
|
|
8229
9876
|
path: "/{session_id}/turns/{turn_id}/events",
|
|
8230
|
-
tags: [
|
|
9877
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8231
9878
|
summary: "List turn events",
|
|
8232
9879
|
description: "Paginated persisted events for a turn (insertion order by default). Only the session creator (`created_by`) may list events.",
|
|
8233
9880
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -8259,7 +9906,7 @@ var listTurnEventsRoute = createRoute12({
|
|
|
8259
9906
|
var createAndExecuteTurnRoute = createRoute12({
|
|
8260
9907
|
method: "post",
|
|
8261
9908
|
path: "/{session_id}/turns",
|
|
8262
|
-
tags: [
|
|
9909
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8263
9910
|
summary: "Create and execute a turn in a session",
|
|
8264
9911
|
description: `Create a turn within a session and execute it.
|
|
8265
9912
|
Only the session creator (\`created_by\`) may create turns.
|
|
@@ -8323,7 +9970,7 @@ Use \`previous_turn_id\` to chain to the session's last turn (defaults to \`auto
|
|
|
8323
9970
|
var subscribeTurnRoute = createRoute12({
|
|
8324
9971
|
method: "get",
|
|
8325
9972
|
path: "/{session_id}/turns/{turn_id}/subscribe",
|
|
8326
|
-
tags: [
|
|
9973
|
+
tags: ["Agent Sessions" /* AGENT_SESSIONS */],
|
|
8327
9974
|
summary: "Subscribe to a running turn",
|
|
8328
9975
|
description: "Subscribe to the live SSE stream for a turn. Only the session creator (`created_by`) may subscribe. Pass `after_sequence_number` to resume after a disconnect (exclusive \u2014 events after this sequence number are replayed).",
|
|
8329
9976
|
"x-fern-sdk-group-name": ["sessions"],
|
|
@@ -8604,7 +10251,7 @@ var StreamGoneError = class extends Error {
|
|
|
8604
10251
|
};
|
|
8605
10252
|
|
|
8606
10253
|
// src/runtime/sandboxFilePath.ts
|
|
8607
|
-
import { SandboxError, validateNoPathTraversal } from "@truefoundry/trueforge-core/core";
|
|
10254
|
+
import { SandboxError, validateNoPathTraversal as validateNoPathTraversal3 } from "@truefoundry/trueforge-core/core";
|
|
8608
10255
|
var SandboxInvalidPathError = class extends SandboxError {
|
|
8609
10256
|
statusCode = 400;
|
|
8610
10257
|
constructor({ path: path7, reason }) {
|
|
@@ -8630,7 +10277,7 @@ function validateSandboxFilePath(path7) {
|
|
|
8630
10277
|
reason: `must not have a segment longer than ${String(MAX_SEGMENT_BYTES)} bytes`
|
|
8631
10278
|
});
|
|
8632
10279
|
}
|
|
8633
|
-
|
|
10280
|
+
validateNoPathTraversal3(path7);
|
|
8634
10281
|
}
|
|
8635
10282
|
|
|
8636
10283
|
// src/apis/turns.ts
|
|
@@ -8667,7 +10314,8 @@ function createTurnResolver(deps) {
|
|
|
8667
10314
|
modelProviderStore,
|
|
8668
10315
|
logger,
|
|
8669
10316
|
signal,
|
|
8670
|
-
userRef
|
|
10317
|
+
userRef,
|
|
10318
|
+
sessionId
|
|
8671
10319
|
} = deps;
|
|
8672
10320
|
return new TurnResourceResolver({
|
|
8673
10321
|
llm: async (name) => {
|
|
@@ -8707,14 +10355,19 @@ function createTurnResolver(deps) {
|
|
|
8707
10355
|
const provider = await resolveSandboxProvider({
|
|
8708
10356
|
tenant_id: TENANT_ID,
|
|
8709
10357
|
store: sandboxProviderStore,
|
|
8710
|
-
logger
|
|
10358
|
+
logger,
|
|
10359
|
+
sessionId
|
|
8711
10360
|
});
|
|
8712
10361
|
if (provider === void 0) {
|
|
8713
10362
|
throw new HTTPException4(422, {
|
|
8714
10363
|
message: "no sandbox provider configured \u2014 PUT /settings/sandbox-providers"
|
|
8715
10364
|
});
|
|
8716
10365
|
}
|
|
8717
|
-
|
|
10366
|
+
const carriedSandboxId = existingSandboxIdForProvider({
|
|
10367
|
+
existingSandboxId,
|
|
10368
|
+
currentProviderType: provider.type
|
|
10369
|
+
});
|
|
10370
|
+
if (carriedSandboxId === void 0 && provider.type !== "local") {
|
|
8718
10371
|
const status = await checkSnapshotStatus({ store: sandboxProviderStore, tenant_id: TENANT_ID, logger });
|
|
8719
10372
|
if (status?.status !== "ready") {
|
|
8720
10373
|
throw new HTTPException4(422, {
|
|
@@ -8732,9 +10385,8 @@ function createTurnResolver(deps) {
|
|
|
8732
10385
|
logger,
|
|
8733
10386
|
gitSkills,
|
|
8734
10387
|
fileDownloadEnabled: spec.config.sandbox.file_downloads,
|
|
8735
|
-
existingSandboxId,
|
|
8736
|
-
tracing
|
|
8737
|
-
tenantName: TENANT_ID
|
|
10388
|
+
existingSandboxId: carriedSandboxId,
|
|
10389
|
+
tracing
|
|
8738
10390
|
});
|
|
8739
10391
|
},
|
|
8740
10392
|
agent: async (agentId) => {
|
|
@@ -8884,13 +10536,13 @@ function createTurnsRouter(deps) {
|
|
|
8884
10536
|
const provider = await resolveSandboxProvider({
|
|
8885
10537
|
tenant_id: TENANT_ID,
|
|
8886
10538
|
store: deps.sandboxProviderStore,
|
|
8887
|
-
logger: deps.logger
|
|
10539
|
+
logger: deps.logger,
|
|
10540
|
+
sessionId
|
|
8888
10541
|
});
|
|
8889
10542
|
if (provider === void 0) {
|
|
8890
10543
|
return c.json({ error: { message: "No sandbox provider configured" } }, 412);
|
|
8891
10544
|
}
|
|
8892
|
-
|
|
8893
|
-
const content = await provider.downloadFile({ sandboxId, path: path7 });
|
|
10545
|
+
const content = await provider.downloadFile({ sandboxId: rawSandboxId(sandboxId), path: path7 });
|
|
8894
10546
|
return c.body(toArrayBuffer(content), 200, {
|
|
8895
10547
|
"Content-Type": "application/octet-stream",
|
|
8896
10548
|
"Content-Length": String(content.byteLength),
|
|
@@ -8959,7 +10611,8 @@ function createTurnsRouter(deps) {
|
|
|
8959
10611
|
modelProviderStore: deps.modelProviderStore,
|
|
8960
10612
|
logger: deps.logger,
|
|
8961
10613
|
signal: abortController.signal,
|
|
8962
|
-
userRef: deps.resolveUserContext(c).userRef
|
|
10614
|
+
userRef: deps.resolveUserContext(c).userRef,
|
|
10615
|
+
sessionId
|
|
8963
10616
|
});
|
|
8964
10617
|
const title = session.record.last_turn_id ? void 0 : deriveSessionTitle(body.input);
|
|
8965
10618
|
let turn;
|
|
@@ -9139,9 +10792,9 @@ function readPackageVersion() {
|
|
|
9139
10792
|
var PACKAGE_VERSION = readPackageVersion();
|
|
9140
10793
|
|
|
9141
10794
|
// src/zodErrorResponse.ts
|
|
9142
|
-
import { z as
|
|
10795
|
+
import { z as z25 } from "@hono/zod-openapi";
|
|
9143
10796
|
function zodErrorResponse(c, error) {
|
|
9144
|
-
return c.json({ error: { message:
|
|
10797
|
+
return c.json({ error: { message: z25.prettifyError(error) } }, 400);
|
|
9145
10798
|
}
|
|
9146
10799
|
var zodValidationHook = (result, c) => {
|
|
9147
10800
|
if (!result.success) {
|
|
@@ -9160,7 +10813,7 @@ function createRequestBodyLimitMiddleware(maxSize) {
|
|
|
9160
10813
|
}
|
|
9161
10814
|
function createAppErrorHandler(params) {
|
|
9162
10815
|
return (error, c) => {
|
|
9163
|
-
if (error instanceof
|
|
10816
|
+
if (error instanceof z26.ZodError) {
|
|
9164
10817
|
return zodErrorResponse(c, error);
|
|
9165
10818
|
}
|
|
9166
10819
|
if (error instanceof HTTPException5) {
|
|
@@ -9182,7 +10835,8 @@ var openApiDocConfig = {
|
|
|
9182
10835
|
title: "TrueForge API",
|
|
9183
10836
|
description: "HTTP API for the TrueForge agent server (`/api/v1`). Interactive docs are served at `/api/v1/docs` (OpenAPI JSON at `/api/v1/openapi.json`).\n\n**Authentication:** Standalone deployments (no OIDC) accept requests without credentials \u2014 middleware stamps a local default user. When OIDC is configured, protected routes require a valid `id_token` cookie or `Authorization: Bearer` ID token. There is no built-in API-key scheme; pass custom headers only if your reverse proxy or IdP layer requires them.\n\nCovers DB-backed sessions, the agent registry, settings catalogs, and model/MCP/skill/sandbox providers.",
|
|
9184
10837
|
version: PACKAGE_VERSION
|
|
9185
|
-
}
|
|
10838
|
+
},
|
|
10839
|
+
tags: OPENAPI_DOCUMENT_TAGS
|
|
9186
10840
|
};
|
|
9187
10841
|
function registerOpenApiBearerAuth(app) {
|
|
9188
10842
|
app.openAPIRegistry.registerComponent("securitySchemes", BEARER_AUTH_SCHEME, {
|
|
@@ -9243,7 +10897,7 @@ function createServerApp(deps) {
|
|
|
9243
10897
|
)
|
|
9244
10898
|
);
|
|
9245
10899
|
app.route(
|
|
9246
|
-
"/api/v1/
|
|
10900
|
+
"/api/v1/catalogs",
|
|
9247
10901
|
withAuth(
|
|
9248
10902
|
createCatalogRouter({
|
|
9249
10903
|
modelCatalog: deps.modelCatalog,
|
|
@@ -9613,7 +11267,7 @@ init_config();
|
|
|
9613
11267
|
import { serveStatic } from "@hono/node-server/serve-static";
|
|
9614
11268
|
import { every } from "hono/combine";
|
|
9615
11269
|
import { compress } from "hono/compress";
|
|
9616
|
-
import { existsSync as
|
|
11270
|
+
import { existsSync as existsSync5 } from "fs";
|
|
9617
11271
|
import path3 from "path";
|
|
9618
11272
|
var SERVER_PATH_PREFIXES = ["/api", "/healthz"];
|
|
9619
11273
|
var HASHED_ASSET_PREFIX = "/assets/";
|
|
@@ -9623,7 +11277,7 @@ function isServerPath(pathname) {
|
|
|
9623
11277
|
return SERVER_PATH_PREFIXES.some((prefix) => pathname === prefix || pathname.startsWith(`${prefix}/`));
|
|
9624
11278
|
}
|
|
9625
11279
|
function mountFrontend(app, dir) {
|
|
9626
|
-
if (!
|
|
11280
|
+
if (!existsSync5(path3.join(dir, "index.html"))) {
|
|
9627
11281
|
return false;
|
|
9628
11282
|
}
|
|
9629
11283
|
const serveFile = serveStatic({ root: dir, precompressed: true });
|
|
@@ -9756,9 +11410,9 @@ var ActiveTurnRegistry = class {
|
|
|
9756
11410
|
*/
|
|
9757
11411
|
track(input) {
|
|
9758
11412
|
const key = activeTurnKey(input.sessionId, input.turnId);
|
|
9759
|
-
const { promise: waitUntilCompleted, resolve } = Promise.withResolvers();
|
|
11413
|
+
const { promise: waitUntilCompleted, resolve: resolve3 } = Promise.withResolvers();
|
|
9760
11414
|
const markCompleted = () => {
|
|
9761
|
-
|
|
11415
|
+
resolve3(void 0);
|
|
9762
11416
|
};
|
|
9763
11417
|
const run = {
|
|
9764
11418
|
abortController: input.abortController,
|
|
@@ -9948,7 +11602,7 @@ try {
|
|
|
9948
11602
|
}
|
|
9949
11603
|
async function createStandalonePersistence(options) {
|
|
9950
11604
|
const { sqlitePath, logger } = options;
|
|
9951
|
-
await
|
|
11605
|
+
await mkdir5(path6.dirname(sqlitePath), { recursive: true });
|
|
9952
11606
|
const [{ createSqliteDb: createSqliteDb2 }, { migrateSqliteToLatest: migrateSqliteToLatest2 }, sqliteStores] = await Promise.all([
|
|
9953
11607
|
Promise.resolve().then(() => (init_client(), client_exports)),
|
|
9954
11608
|
Promise.resolve().then(() => (init_migrateSqlite(), migrateSqlite_exports)),
|
|
@@ -10097,6 +11751,26 @@ try {
|
|
|
10097
11751
|
});
|
|
10098
11752
|
if (configuration2.STANDALONE) {
|
|
10099
11753
|
printStandaloneStartupBanner({ version: PACKAGE_VERSION, color: shouldColorize() });
|
|
11754
|
+
await prepareCodeModeSocketParent({ path: configuration2.CODE_MODE_SOCKET_PARENT, logger });
|
|
11755
|
+
await ensureLocalSandboxRootParent(configuration2.LOCAL_SANDBOX_ROOT_PARENT);
|
|
11756
|
+
const { LocalSandboxProvider: LocalSandboxProvider2 } = await Promise.resolve().then(() => (init_LocalSandboxProvider(), LocalSandboxProvider_exports));
|
|
11757
|
+
const support = await LocalSandboxProvider2.isSupported({
|
|
11758
|
+
codeModeSocketParentPath: configuration2.CODE_MODE_SOCKET_PARENT
|
|
11759
|
+
});
|
|
11760
|
+
setCachedLocalSandboxSupport(support);
|
|
11761
|
+
if (support.supported) {
|
|
11762
|
+
logger.info("Local sandbox fallback is available", {
|
|
11763
|
+
platform: support.platform,
|
|
11764
|
+
shell: support.shell,
|
|
11765
|
+
python: support.python
|
|
11766
|
+
});
|
|
11767
|
+
} else {
|
|
11768
|
+
logger.warn("Local sandbox fallback is unavailable", {
|
|
11769
|
+
reason: support.reason,
|
|
11770
|
+
...support.platform === void 0 ? {} : { platform: support.platform },
|
|
11771
|
+
...support.attempts === void 0 ? {} : { attempts: support.attempts }
|
|
11772
|
+
});
|
|
11773
|
+
}
|
|
10100
11774
|
} else {
|
|
10101
11775
|
logger.info("TrueForge starting", { mode: "distributed" });
|
|
10102
11776
|
}
|
|
@@ -10151,9 +11825,9 @@ try {
|
|
|
10151
11825
|
logger.warn(`Drain timed out after ${String(configuration2.GRACEFUL_TIMEOUT_SECONDS)}s, exiting`);
|
|
10152
11826
|
process.exit(1);
|
|
10153
11827
|
}, configuration2.GRACEFUL_TIMEOUT_SECONDS * 1e3).unref();
|
|
10154
|
-
const closed = new Promise((
|
|
11828
|
+
const closed = new Promise((resolve3) => {
|
|
10155
11829
|
server.close(() => {
|
|
10156
|
-
|
|
11830
|
+
resolve3();
|
|
10157
11831
|
});
|
|
10158
11832
|
});
|
|
10159
11833
|
await activeTurns.shutdownAndWait(CancellationReason4.Abandoned);
|
|
@@ -10165,6 +11839,11 @@ try {
|
|
|
10165
11839
|
await redis?.close().catch((error) => {
|
|
10166
11840
|
logger.warn("[Redis] Error closing client during shutdown", extractErrorLogFields9(error));
|
|
10167
11841
|
});
|
|
11842
|
+
if (configuration2.STANDALONE) {
|
|
11843
|
+
await removeCodeModeSocketParent(configuration2.CODE_MODE_SOCKET_PARENT).catch((error) => {
|
|
11844
|
+
logger.warn("Error removing Code Mode socket parent during shutdown", extractErrorLogFields9(error));
|
|
11845
|
+
});
|
|
11846
|
+
}
|
|
10168
11847
|
await destroyDb();
|
|
10169
11848
|
process.exit(0);
|
|
10170
11849
|
};
|