@nylorun/runtime 0.25.0-beta → 0.27.0-beta

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.
Files changed (326) hide show
  1. package/CHANGELOG.md +169 -0
  2. package/README.md +26 -16
  3. package/dist/adapters/execution/restate.d.ts +15 -18
  4. package/dist/adapters/execution/restate.js +31 -27
  5. package/dist/adapters/replication/pgoutput.d.ts +1 -1
  6. package/dist/adapters/replication/pgoutput.js +3 -2
  7. package/dist/adapters/sandbox/local.js +8 -5
  8. package/dist/adapters/sandbox/virtual.js +2 -2
  9. package/dist/adapters/streams/s2.d.ts +1 -1
  10. package/dist/adapters/streams/s2.js +18 -14
  11. package/dist/api/a2a/card.js +1 -1
  12. package/dist/api/a2a/endpoint.js +3 -4
  13. package/dist/api/a2a/protocol.js +4 -0
  14. package/dist/api/a2a/routes.d.ts +2 -6
  15. package/dist/api/a2a/routes.js +12 -18
  16. package/dist/api/ag-ui/endpoint.js +2 -3
  17. package/dist/api/ag-ui/routes.js +4 -4
  18. package/dist/api/components.d.ts +326 -45
  19. package/dist/api/components.js +5 -1
  20. package/dist/api/gate/keys.js +7 -4
  21. package/dist/api/gate/routes.d.ts +6 -2
  22. package/dist/api/gate/routes.js +15 -11
  23. package/dist/api/http/app.d.ts +2 -2
  24. package/dist/api/http/app.js +2 -2
  25. package/dist/api/http/body.d.ts +15 -3
  26. package/dist/api/http/body.js +15 -7
  27. package/dist/api/http/define.d.ts +1 -1
  28. package/dist/api/http/respond.js +7 -3
  29. package/dist/api/http/routes/access.js +5 -4
  30. package/dist/api/http/routes/artifacts.js +19 -7
  31. package/dist/api/http/routes/coverage.js +1 -1
  32. package/dist/api/http/routes/environment-manifests.js +1 -1
  33. package/dist/api/http/routes/environments.js +6 -6
  34. package/dist/api/http/routes/files.d.ts +2 -1
  35. package/dist/api/http/routes/files.js +31 -5
  36. package/dist/api/http/routes/mcp.js +1 -1
  37. package/dist/api/http/routes/reads.d.ts +35 -0
  38. package/dist/api/http/routes/reads.js +50 -22
  39. package/dist/api/http/routes/sessions.js +12 -11
  40. package/dist/api/http/routes/tenant.d.ts +2 -2
  41. package/dist/api/http/routes/tenant.js +69 -16
  42. package/dist/api/inbound-files.d.ts +3 -9
  43. package/dist/api/inbound-files.js +18 -24
  44. package/dist/api/openapi.js +6 -2
  45. package/dist/artifacts/disk.d.ts +0 -4
  46. package/dist/artifacts/disk.js +77 -73
  47. package/dist/artifacts/files.d.ts +13 -4
  48. package/dist/artifacts/files.js +27 -9
  49. package/dist/artifacts/folder-upload.d.ts +75 -0
  50. package/dist/artifacts/folder-upload.js +263 -0
  51. package/dist/artifacts/folders.d.ts +14 -8
  52. package/dist/artifacts/folders.js +19 -11
  53. package/dist/artifacts/links.js +7 -6
  54. package/dist/artifacts/parts.d.ts +9 -14
  55. package/dist/artifacts/parts.js +11 -9
  56. package/dist/artifacts/pipe.d.ts +15 -0
  57. package/dist/artifacts/pipe.js +71 -0
  58. package/dist/artifacts/quota.d.ts +36 -0
  59. package/dist/artifacts/quota.js +44 -0
  60. package/dist/artifacts/service.d.ts +25 -1
  61. package/dist/artifacts/service.js +70 -25
  62. package/dist/blob/fs.js +2 -1
  63. package/dist/blob/s3.d.ts +6 -2
  64. package/dist/blob/s3.js +9 -7
  65. package/dist/blob/types.d.ts +1 -1
  66. package/dist/blob/types.js +1 -1
  67. package/dist/core/limits.d.ts +11 -35
  68. package/dist/core/limits.js +5 -73
  69. package/dist/core/sandbox-routes.js +0 -1
  70. package/dist/core/turn-manifest.d.ts +1 -2
  71. package/dist/core/turn-manifest.js +4 -28
  72. package/dist/execution/memory.d.ts +8 -5
  73. package/dist/execution/memory.js +8 -6
  74. package/dist/execution/types.d.ts +1 -6
  75. package/dist/execution/types.js +0 -5
  76. package/dist/gates/contract.d.ts +3 -2
  77. package/dist/gates/contract.js +1 -1
  78. package/dist/gates/egress-credentials.js +4 -3
  79. package/dist/gates/egress.d.ts +9 -9
  80. package/dist/gates/egress.js +19 -19
  81. package/dist/gates/fixture-model.js +13 -2
  82. package/dist/gates/handler.d.ts +1 -3
  83. package/dist/gates/handler.js +4 -3
  84. package/dist/gates/http-client.d.ts +4 -3
  85. package/dist/gates/http-client.js +5 -7
  86. package/dist/gates/http-tool.d.ts +24 -4
  87. package/dist/gates/http-tool.js +53 -17
  88. package/dist/gates/in-process.d.ts +2 -1
  89. package/dist/gates/in-process.js +17 -9
  90. package/dist/gates/inflight.d.ts +2 -2
  91. package/dist/gates/inflight.js +3 -2
  92. package/dist/gates/journal.js +5 -7
  93. package/dist/gates/mcp-handler.d.ts +4 -3
  94. package/dist/gates/mcp-handler.js +15 -5
  95. package/dist/gates/model-gate.d.ts +11 -2
  96. package/dist/gates/policy.d.ts +28 -5
  97. package/dist/gates/policy.js +60 -17
  98. package/dist/gates/provider-gate.d.ts +22 -0
  99. package/dist/gates/provider-gate.js +30 -0
  100. package/dist/gates/tool-calls.d.ts +1 -3
  101. package/dist/gates/tool-calls.js +2 -3
  102. package/dist/gates/tool-client.d.ts +1 -1
  103. package/dist/gates/tool-client.js +5 -6
  104. package/dist/gates/tool-contract.d.ts +8 -5
  105. package/dist/gates/tool-contract.js +1 -1
  106. package/dist/gates/tool-gate.d.ts +18 -7
  107. package/dist/gates/tool-gate.js +49 -7
  108. package/dist/harness/calls.d.ts +27 -25
  109. package/dist/harness/calls.js +40 -46
  110. package/dist/harness/executors.d.ts +10 -3
  111. package/dist/harness/executors.js +53 -23
  112. package/dist/harness/main.js +2 -2
  113. package/dist/harness/pod.d.ts +0 -2
  114. package/dist/harness/pod.js +7 -6
  115. package/dist/harness/run-tokens.d.ts +4 -1
  116. package/dist/harness/run-tokens.js +6 -2
  117. package/dist/harness/service.d.ts +17 -14
  118. package/dist/harness/service.js +21 -35
  119. package/dist/harness/setup.d.ts +0 -2
  120. package/dist/harness/setup.js +4 -9
  121. package/dist/harness/ws-client.d.ts +2 -2
  122. package/dist/harness/ws-client.js +7 -5
  123. package/dist/harness/ws-port.d.ts +3 -4
  124. package/dist/harness/ws-port.js +3 -4
  125. package/dist/harness-api/artifacts.d.ts +20 -0
  126. package/dist/harness-api/artifacts.js +478 -0
  127. package/dist/harness-api/in-process.d.ts +2 -2
  128. package/dist/harness-api/in-process.js +18 -16
  129. package/dist/harness-api/record.d.ts +10 -9
  130. package/dist/harness-api/record.js +102 -157
  131. package/dist/harness-api/renew.d.ts +1 -1
  132. package/dist/harness-api/renew.js +2 -2
  133. package/dist/harness-api/server.d.ts +1 -16
  134. package/dist/harness-api/server.js +82 -73
  135. package/dist/harness-api/start.d.ts +20 -2
  136. package/dist/harness-api/start.js +57 -18
  137. package/dist/harness-api/tool-journal.d.ts +15 -10
  138. package/dist/harness-api/tool-journal.js +26 -24
  139. package/dist/harness-api/workspace.d.ts +0 -4
  140. package/dist/harness-api/workspace.js +2 -45
  141. package/dist/harness-api/ws-server.js +10 -10
  142. package/dist/host/create-host.js +2 -3
  143. package/dist/host/execution.js +2 -3
  144. package/dist/host/gates.d.ts +2 -9
  145. package/dist/host/gates.js +15 -12
  146. package/dist/host/http.d.ts +0 -2
  147. package/dist/host/http.js +0 -27
  148. package/dist/host/main.js +10 -3
  149. package/dist/host/operate.js +12 -3
  150. package/dist/host/stack-config.d.ts +11 -0
  151. package/dist/host/stack-config.js +21 -1
  152. package/dist/index.d.ts +0 -1
  153. package/dist/index.js +0 -1
  154. package/dist/infra/index.js +2 -1
  155. package/dist/infra/readiness.d.ts +0 -2
  156. package/dist/infra/readiness.js +4 -3
  157. package/dist/keys/client.d.ts +1 -0
  158. package/dist/keys/client.js +2 -3
  159. package/dist/keys/contract.d.ts +1 -3
  160. package/dist/keys/contract.js +1 -3
  161. package/dist/keys/egress-ca.d.ts +0 -2
  162. package/dist/keys/egress-ca.js +2 -3
  163. package/dist/keys/keys.js +6 -1
  164. package/dist/keys/x509.d.ts +8 -4
  165. package/dist/keys/x509.js +14 -5
  166. package/dist/management-openapi.json +1961 -994
  167. package/dist/mcp/connect.d.ts +35 -26
  168. package/dist/mcp/connect.js +112 -141
  169. package/dist/mcp/pool.d.ts +27 -22
  170. package/dist/mcp/pool.js +34 -48
  171. package/dist/mcp/preview.d.ts +13 -6
  172. package/dist/mcp/preview.js +37 -20
  173. package/dist/mcp/snapshot.d.ts +35 -86
  174. package/dist/mcp/snapshot.js +93 -135
  175. package/dist/model/auth-store.js +5 -3
  176. package/dist/model/capabilities.d.ts +1 -1
  177. package/dist/model/catalog.d.ts +9 -36
  178. package/dist/model/catalog.js +6 -33
  179. package/dist/model/descriptor.d.ts +51 -0
  180. package/dist/model/descriptor.js +83 -0
  181. package/dist/model/models.d.ts +0 -3
  182. package/dist/model/models.js +3 -5
  183. package/dist/model/native-blocks.js +1 -1
  184. package/dist/model/pi-model.d.ts +16 -5
  185. package/dist/model/pi-model.js +86 -105
  186. package/dist/openapi.json +866 -1218
  187. package/dist/reads/cursor.js +3 -1
  188. package/dist/record/append.d.ts +34 -2
  189. package/dist/record/append.js +50 -1
  190. package/dist/record/environment.d.ts +2 -0
  191. package/dist/record/environment.js +2 -0
  192. package/dist/record/index.d.ts +1 -1
  193. package/dist/record/index.js +1 -1
  194. package/dist/redact.d.ts +1 -1
  195. package/dist/redact.js +3 -2
  196. package/dist/sandbox/egress-token.d.ts +3 -3
  197. package/dist/sandbox/egress-token.js +3 -3
  198. package/dist/sandbox/join.js +3 -2
  199. package/dist/sandbox/manager.d.ts +41 -41
  200. package/dist/sandbox/manager.js +112 -44
  201. package/dist/sandbox/placement.d.ts +1 -1
  202. package/dist/sandbox/placement.js +5 -8
  203. package/dist/sandbox/pods/client.d.ts +1 -10
  204. package/dist/sandbox/pods/client.js +8 -4
  205. package/dist/sandbox/pods/lifecycle.d.ts +19 -11
  206. package/dist/sandbox/pods/lifecycle.js +45 -23
  207. package/dist/sandbox/pods/network-gate.d.ts +4 -5
  208. package/dist/sandbox/pods/network-gate.js +7 -7
  209. package/dist/sandbox/pods/reconcile.js +4 -1
  210. package/dist/sandbox/pods/spec.js +8 -5
  211. package/dist/sandbox/policy.d.ts +7 -1
  212. package/dist/sandbox/policy.js +10 -3
  213. package/dist/sandbox/resolve.d.ts +8 -3
  214. package/dist/sandbox/resolve.js +12 -2
  215. package/dist/sandbox/setup.d.ts +3 -1
  216. package/dist/sandbox/setup.js +34 -11
  217. package/dist/sandbox/share.d.ts +5 -14
  218. package/dist/sandbox/share.js +5 -34
  219. package/dist/sandbox/skills.d.ts +7 -13
  220. package/dist/sandbox/skills.js +16 -31
  221. package/dist/sandbox/tenant-config.d.ts +2 -2
  222. package/dist/sandbox/tenant-config.js +4 -3
  223. package/dist/sandbox/tools.d.ts +7 -1
  224. package/dist/sandbox/tools.js +26 -26
  225. package/dist/sandbox/types.d.ts +10 -3
  226. package/dist/sandbox/types.js +9 -0
  227. package/dist/store/postgres/connect.d.ts +6 -5
  228. package/dist/store/postgres/connect.js +14 -11
  229. package/dist/store/postgres/control.js +13 -13
  230. package/dist/store/postgres/drizzle/0028_artifact_write_keys.sql +2 -0
  231. package/dist/store/postgres/drizzle/0029_protocol_15.sql +11 -0
  232. package/dist/store/postgres/drizzle/0030_protocol_15_runtime_block.sql +11 -0
  233. package/dist/store/postgres/drizzle/0031_protocol_15_mcp_snapshot.sql +8 -0
  234. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  235. package/dist/store/postgres/migrate.js +1 -1
  236. package/dist/store/postgres/reads.js +10 -2
  237. package/dist/store/postgres/schema.d.ts +19 -0
  238. package/dist/store/postgres/schema.js +7 -0
  239. package/dist/store/postgres/store.js +55 -26
  240. package/dist/store/types.d.ts +17 -7
  241. package/dist/streams/relay/core.d.ts +3 -3
  242. package/dist/streams/relay/core.js +9 -10
  243. package/dist/tenant/advance.js +21 -31
  244. package/dist/tenant/commands.js +9 -16
  245. package/dist/tenant/context.d.ts +14 -17
  246. package/dist/tenant/definition-files.d.ts +25 -4
  247. package/dist/tenant/definition-files.js +85 -47
  248. package/dist/tenant/effects.js +9 -36
  249. package/dist/tenant/environment-manifests.js +9 -1
  250. package/dist/tenant/environments.d.ts +0 -2
  251. package/dist/tenant/environments.js +54 -30
  252. package/dist/tenant/ephemeral.d.ts +1 -1
  253. package/dist/tenant/ephemeral.js +2 -1
  254. package/dist/tenant/history.d.ts +1 -1
  255. package/dist/tenant/history.js +1 -1
  256. package/dist/tenant/host-token.d.ts +3 -3
  257. package/dist/tenant/host-token.js +12 -11
  258. package/dist/tenant/http.d.ts +5 -8
  259. package/dist/tenant/http.js +9 -0
  260. package/dist/tenant/identity-file.d.ts +24 -1
  261. package/dist/tenant/identity-file.js +35 -7
  262. package/dist/tenant/issuers.d.ts +5 -5
  263. package/dist/tenant/issuers.js +31 -23
  264. package/dist/tenant/jwt.d.ts +8 -2
  265. package/dist/tenant/jwt.js +10 -2
  266. package/dist/tenant/limits.d.ts +90 -0
  267. package/dist/tenant/limits.js +175 -0
  268. package/dist/tenant/module.d.ts +4 -1
  269. package/dist/tenant/module.js +9 -8
  270. package/dist/tenant/outbound.d.ts +17 -19
  271. package/dist/tenant/outbound.js +18 -21
  272. package/dist/tenant/run-grants.js +3 -2
  273. package/dist/tenant/run-token.d.ts +0 -4
  274. package/dist/tenant/run-token.js +6 -11
  275. package/dist/tenant/runtime-limits.d.ts +24 -0
  276. package/dist/tenant/runtime-limits.js +27 -0
  277. package/dist/tenant/runtime.js +30 -43
  278. package/dist/tenant/session-streams.js +7 -6
  279. package/dist/tenant/session.d.ts +1 -1
  280. package/dist/tenant/session.js +1 -1
  281. package/dist/tenant/sessions.js +32 -29
  282. package/dist/tenant/signing-keys.d.ts +1 -1
  283. package/dist/tenant/signing-keys.js +5 -6
  284. package/dist/tenant/status.js +4 -3
  285. package/dist/tenant/streams.js +18 -20
  286. package/dist/tenant/sweep.d.ts +1 -21
  287. package/dist/tenant/sweep.js +39 -48
  288. package/dist/tenant/transcript.d.ts +70 -23
  289. package/dist/tenant/transcript.js +28 -22
  290. package/dist/tenant/types.d.ts +11 -16
  291. package/dist/tenant/worker.d.ts +2 -10
  292. package/dist/tenant/worker.js +5 -14
  293. package/dist/traces/ids.js +5 -1
  294. package/dist/traces/map.d.ts +9 -6
  295. package/dist/traces/map.js +9 -11
  296. package/dist/traces/otlp.d.ts +3 -3
  297. package/dist/traces/otlp.js +5 -3
  298. package/dist/traces/projector.d.ts +9 -4
  299. package/dist/traces/projector.js +13 -7
  300. package/dist/traces/read.js +6 -1
  301. package/dist/tuning.d.ts +383 -0
  302. package/dist/tuning.js +658 -0
  303. package/dist/vault/headers.js +4 -2
  304. package/dist/version.d.ts +1 -1
  305. package/dist/version.js +1 -1
  306. package/package.json +3 -3
  307. package/dist/artifacts/export.d.ts +0 -8
  308. package/dist/artifacts/export.js +0 -204
  309. package/dist/artifacts/media-types.d.ts +0 -30
  310. package/dist/artifacts/media-types.js +0 -137
  311. package/dist/artifacts/workspace.d.ts +0 -60
  312. package/dist/artifacts/workspace.js +0 -45
  313. package/dist/media.d.ts +0 -10
  314. package/dist/media.js +0 -58
  315. package/dist/sandbox/runtime-tools.d.ts +0 -9
  316. package/dist/sandbox/runtime-tools.js +0 -16
  317. package/dist/sandbox/session-sandbox.d.ts +0 -16
  318. package/dist/sandbox/session-sandbox.js +0 -77
  319. package/dist/tenant/artifact-tool.d.ts +0 -13
  320. package/dist/tenant/artifact-tool.js +0 -243
  321. package/dist/tenant/skill-tool.d.ts +0 -20
  322. package/dist/tenant/skill-tool.js +0 -67
  323. package/dist/tenant/tool-results.d.ts +0 -57
  324. package/dist/tenant/tool-results.js +0 -298
  325. package/dist/tenant/tool-search.d.ts +0 -39
  326. package/dist/tenant/tool-search.js +0 -103
@@ -1,3 +1,4 @@
1
+ import { hostTuning } from "../tuning.js";
1
2
  /** A re-send whose request differs from the call already running under its key. */
2
3
  export class InflightConflict extends Error {
3
4
  name = "InflightConflict";
@@ -7,8 +8,8 @@ export class InflightStale extends Error {
7
8
  name = "InflightStale";
8
9
  }
9
10
  export function createInflightCalls(options = {}) {
10
- const ttlMs = options.ttlMs ?? 30 * 60_000;
11
- const max = options.max ?? 10_000;
11
+ const ttlMs = options.ttlMs ?? hostTuning().gates.inflightTtlMs;
12
+ const max = options.max ?? hostTuning().gates.inflightMax;
12
13
  const now = options.now ?? Date.now;
13
14
  const entries = new Map();
14
15
  /** Drops expired outcomes, then the oldest settled ones while over `max`. */
@@ -1,4 +1,3 @@
1
- import { mcpToolOf } from "../mcp/snapshot.js";
2
1
  import { canonical } from "../store/canonical.js";
3
2
  import { httpToolRefOf } from "./http-tool.js";
4
3
  export function journalCheck(vaults) {
@@ -25,12 +24,11 @@ export function journalCheck(vaults) {
25
24
  return `Effect '${key}' was journaled with other input`;
26
25
  return held(row);
27
26
  }
28
- const session = await t.get("sessions", request.sessionId);
29
- const tool = mcpToolOf(session?.mcpSnapshot, request);
30
- if (!tool ||
31
- tool.serverName !== call.server.serverName ||
32
- (tool.agentId ?? null) !== (call.server.agentId ?? null) ||
33
- tool.serverToolName !== call.name)
27
+ const target = request.target;
28
+ if (target?.kind !== "mcp" ||
29
+ target.server !== call.server.serverName ||
30
+ target.tool !== call.name ||
31
+ (request.agent?.id ?? null) !== (call.server.agentId ?? null))
34
32
  return `Effect '${key}' calls another tool`;
35
33
  if (canonical(argumentsOf(request.input)) !== canonical(call.arguments))
36
34
  return `Effect '${key}' was journaled with other input`;
@@ -4,12 +4,13 @@ import type { OutboundPolicy } from "../tenant/outbound.js";
4
4
  import type { Logger } from "../tenant/types.js";
5
5
  import type { McpAnswer } from "./tool-contract.js";
6
6
  import { type TenantVaults } from "./tenant-vaults.js";
7
- /** A connection unused this long is closed; the next request opens it again. */
8
- export declare const GATE_MCP_IDLE_MS: number;
9
7
  export interface McpHandlerOptions {
10
8
  readonly vaults: TenantVaults;
11
9
  readonly logger: Logger;
12
- /** Default `GATE_MCP_IDLE_MS`. */
10
+ /**
11
+ * A connection unused this long is closed; the next request opens it again. Default
12
+ * `gates.mcpIdleMs` (Host tuning, 15 min).
13
+ */
13
14
  readonly idleMs?: number;
14
15
  readonly now?: () => number;
15
16
  /** Tests replace how a server is opened. */
@@ -1,14 +1,14 @@
1
1
  import { McpCallFailed, McpConnectionStale, openMcpServer, unreachable, } from "../mcp/connect.js";
2
2
  import { findServer } from "../mcp/pool.js";
3
+ import { readRuntimeLimits } from "../tenant/runtime-limits.js";
3
4
  import { GateRefusal } from "./tenant-vaults.js";
4
- /** A connection unused this long is closed; the next request opens it again. */
5
- export const GATE_MCP_IDLE_MS = 15 * 60_000;
5
+ import { hostTuning } from "../tuning.js";
6
6
  /** The server is not one the session may reach through the gate. */
7
7
  class NotDeclared extends Error {
8
8
  }
9
9
  export function createMcpHandler(options) {
10
10
  const { vaults, logger } = options;
11
- const idleMs = options.idleMs ?? GATE_MCP_IDLE_MS;
11
+ const idleMs = options.idleMs ?? hostTuning().gates.mcpIdleMs;
12
12
  const now = options.now ?? Date.now;
13
13
  const open = options.open ?? openMcpServer;
14
14
  const live = new Map();
@@ -42,6 +42,7 @@ export function createMcpHandler(options) {
42
42
  ...(server.agentId === undefined ? {} : { agentId: server.agentId }),
43
43
  }),
44
44
  policy: options.delivery ?? {},
45
+ errorExcerptChars: (await vault.store.tx((t) => readRuntimeLimits(t))).errorExcerptChars,
45
46
  });
46
47
  const entry = { connection, lastUsedAt: now(), active: 0 };
47
48
  live.set(key, entry);
@@ -80,7 +81,11 @@ export function createMcpHandler(options) {
80
81
  // the session is not the server's failure, and stays as it was.
81
82
  if (!call || error instanceof GateRefusal || error instanceof NotDeclared)
82
83
  throw error;
83
- throw unreachable(server.serverName, error);
84
+ const limits = await vaults
85
+ .open(tenantId)
86
+ .then((vault) => vault.store.tx((t) => readRuntimeLimits(t)))
87
+ .catch(() => undefined);
88
+ throw unreachable(server.serverName, error, limits?.errorExcerptChars);
84
89
  }
85
90
  }
86
91
  async function use(tenantId, server, run, call = false) {
@@ -112,10 +117,15 @@ export function createMcpHandler(options) {
112
117
  * the call sent once more (R2b C7).
113
118
  */
114
119
  async function callOnce(tenantId, request, signal) {
120
+ // The Tenant's Runtime limits, read for this call: the largest answer the gate keeps, and how
121
+ // much of a failed one its error quotes.
122
+ const { toolAnswerBytes, errorExcerptChars } = await (await vaults.open(tenantId)).store.tx((t) => readRuntimeLimits(t));
115
123
  for (let attempt = 0;; attempt += 1) {
116
124
  try {
117
125
  return await use(tenantId, request.server, (entry) => entry.connection.client.callTool({ name: request.name, arguments: request.arguments }, {
118
126
  signal,
127
+ maxResultBytes: toolAnswerBytes,
128
+ errorExcerptChars,
119
129
  ...(request.key === undefined ? {} : { key: request.key }),
120
130
  ...(request.traceparent === undefined ? {} : { traceparent: request.traceparent }),
121
131
  }), true);
@@ -188,7 +198,7 @@ function errorOf(error) {
188
198
  ...(error.code === "credential_rejected" ? { code: 401 } : {}),
189
199
  failure: error.failure,
190
200
  };
191
- const message = error instanceof Error ? error.message.slice(0, 2000) : String(error);
201
+ const message = error instanceof Error ? error.message.slice(0, hostTuning().gates.errorMessageChars) : String(error);
192
202
  const code = error && typeof error === "object" && "code" in error && typeof error.code === "number"
193
203
  ? error.code
194
204
  : undefined;
@@ -4,11 +4,13 @@
4
4
  * call needs that the loop must not hold: the provider credential, pi-ai, its retries and
5
5
  * idle watchdog, failure classification and scrubbing.
6
6
  *
7
- * Two implementations: `inProcessModelGate` (embedding, the ephemeral Runtime, tests) and the
7
+ * Three implementations: `inProcessModelGate` (embedding, the ephemeral Runtime, tests), the
8
8
  * HTTP client of the gates service (the `gateway` container), which keeps the credential out
9
- * of the loop process. Both return what `piModel` returns, so the effect journal is the same
9
+ * of the loop process, and `providerModelGate`, which serves a Runtime's scripted model (tests,
10
+ * `model: {kind: "scripted"}`) the same way, metered. Both return what `piModel` returns, so the effect journal is the same
10
11
  * either way.
11
12
  */
13
+ import type { AgentRef } from "@nylorun/core/define";
12
14
  import type { RuntimeModelAdapter, RuntimeModelCall } from "../contracts.js";
13
15
  export type { ModelCallSettings } from "../model/pi-model.js";
14
16
  /**
@@ -25,6 +27,13 @@ export interface ModelGateRequest {
25
27
  /** The effect's journal id; sent as `Idempotency-Key` (acted on from P1.2). */
26
28
  readonly effectId: string;
27
29
  readonly invocationId: string;
30
+ /**
31
+ * The agent used as a tool the call is for; absent for the session's root agent. In process
32
+ * only: a scripted provider reads it (`providerModelGate`).
33
+ */
34
+ readonly agent?: AgentRef;
35
+ /** A call that summarizes history for a compaction (protocol 15); in process only, like `agent`. */
36
+ readonly purpose?: "summary";
28
37
  readonly call: RuntimeModelCall;
29
38
  }
30
39
  /** A model candidate or a failure outcome: exactly what `piModel` returns. */
@@ -10,11 +10,17 @@
10
10
  * (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
11
11
  * manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
12
12
  * the approval it was opened with. A policy that cannot be read refuses every call.
13
+ *
14
+ * A rule's `tool` matches the tool's identity (protocol 15): an MCP tool's own name on its
15
+ * server (`server` names the server), an HTTP tool's declared name, a flow stage's key. The name
16
+ * a harness gave the model never matches. A stored rule whose `tool` has `__` in it (an MCP tool
17
+ * named by its old model-facing `server__tool` name) makes the policy unreadable, so every call
18
+ * is refused until the policy names it by `server` and `tool`: a rule never silently stops
19
+ * matching.
13
20
  */
14
21
  import { type ToolPolicy, type ToolRule } from "@nylorun/core/contracts";
15
22
  import type { AgentManifest } from "@nylorun/core/define";
16
23
  import type { HostEffect } from "@nylorun/harness/run";
17
- import { type McpSnapshot } from "../mcp/snapshot.js";
18
24
  import type { Tx } from "../store/types.js";
19
25
  import type { Session } from "../tenant/context.js";
20
26
  /** The `tenant_settings` key the policy is kept under. */
@@ -22,13 +28,18 @@ export declare const TOOL_POLICY_SETTING = "tool-policy";
22
28
  /** A call as the policy reads it. */
23
29
  export interface PolicyCall {
24
30
  readonly kind: "mcp" | "http";
25
- /** The name the model calls the tool by; a flow stage's key. */
31
+ /**
32
+ * The tool's identity: an MCP tool's own name on its server, an HTTP tool's declared name, a
33
+ * flow stage's key.
34
+ */
26
35
  readonly tool: string;
27
36
  /** The MCP server's name. */
28
37
  readonly server?: string;
29
38
  /** The agent that calls it. */
30
39
  readonly agent: string;
31
40
  readonly input: unknown;
41
+ /** The name the model called it by, for display only: no rule matches it. */
42
+ readonly name?: string;
32
43
  }
33
44
  export type PolicyDecision = {
34
45
  readonly effect: "allow";
@@ -36,14 +47,26 @@ export type PolicyDecision = {
36
47
  readonly effect: "ask" | "deny";
37
48
  readonly reason: string;
38
49
  };
39
- /** The Tenant's policy; an empty one when none was set. Throws when it cannot be read. */
50
+ /** A stored policy this Runtime cannot apply: every call is refused, with `message` as why. */
51
+ export declare class ToolPolicyUnreadable extends Error {
52
+ constructor(message: string);
53
+ }
54
+ /**
55
+ * The Tenant's policy; an empty one when none was set. Throws when it cannot be read, a
56
+ * `ToolPolicyUnreadable` saying which rule when one names an MCP tool by a model-facing name.
57
+ */
40
58
  export declare function readToolPolicy(t: Pick<Tx, "getSetting">): Promise<ToolPolicy>;
41
59
  /**
42
60
  * The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
43
61
  * pinned manifest's `approval`. A policy that cannot be read refuses the call.
44
62
  */
45
63
  export declare function toolCallDecision(t: Pick<Tx, "getSetting">, s: Session, request: HostEffect, kind: "mcp" | "http"): Promise<PolicyDecision>;
64
+ /** `request` as the policy reads it: by its target's identity (protocol 15). */
65
+ export declare function policyCallOf(s: Pick<Session, "agentId">, request: HostEffect, kind: "mcp" | "http"): PolicyCall;
46
66
  /** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
47
67
  export declare function decide(rules: readonly ToolRule[], call: PolicyCall, pinnedAsk?: boolean): PolicyDecision;
48
- /** The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2. */
49
- export declare function pinnedApproval(manifest: AgentManifest, snapshot: McpSnapshot | undefined, request: HostEffect): boolean;
68
+ /**
69
+ * The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2: an
70
+ * MCP tool's settings by its own name, an HTTP tool's declaration by its declared name.
71
+ */
72
+ export declare function pinnedApproval(manifest: AgentManifest, request: HostEffect): boolean;
@@ -10,21 +10,44 @@
10
10
  * (`deny`, then `ask`, then `allow`), and a call no rule matches runs. A session pinned to a
11
11
  * manifest saved before R5 step 2 keeps its `approval: "always"` as an ask: no session loses
12
12
  * the approval it was opened with. A policy that cannot be read refuses every call.
13
+ *
14
+ * A rule's `tool` matches the tool's identity (protocol 15): an MCP tool's own name on its
15
+ * server (`server` names the server), an HTTP tool's declared name, a flow stage's key. The name
16
+ * a harness gave the model never matches. A stored rule whose `tool` has `__` in it (an MCP tool
17
+ * named by its old model-facing `server__tool` name) makes the policy unreadable, so every call
18
+ * is refused until the policy names it by `server` and `tool`: a rule never silently stops
19
+ * matching.
13
20
  */
14
21
  import { ToolPolicySchema } from "@nylorun/core/contracts";
15
- import { mcpToolOf, mcpToolSettingsIn } from "../mcp/snapshot.js";
22
+ import { mcpToolSettingsIn } from "../mcp/snapshot.js";
16
23
  import { canonical } from "../store/canonical.js";
17
24
  import { turnManifestOf } from "../tenant/session.js";
18
25
  import { declaredHttpTool } from "./http-tool.js";
19
26
  /** The `tenant_settings` key the policy is kept under. */
20
27
  export const TOOL_POLICY_SETTING = "tool-policy";
21
28
  const STRICTNESS = { allow: 0, ask: 1, deny: 2 };
22
- /** The Tenant's policy; an empty one when none was set. Throws when it cannot be read. */
29
+ /** A stored policy this Runtime cannot apply: every call is refused, with `message` as why. */
30
+ export class ToolPolicyUnreadable extends Error {
31
+ constructor(message) {
32
+ super(message);
33
+ this.name = "ToolPolicyUnreadable";
34
+ }
35
+ }
36
+ /**
37
+ * The Tenant's policy; an empty one when none was set. Throws when it cannot be read, a
38
+ * `ToolPolicyUnreadable` saying which rule when one names an MCP tool by a model-facing name.
39
+ */
23
40
  export async function readToolPolicy(t) {
24
41
  const raw = await t.getSetting(TOOL_POLICY_SETTING);
25
42
  if (raw === undefined)
26
43
  return { rules: [], updatedAt: null };
27
- return ToolPolicySchema.parse(JSON.parse(raw));
44
+ const parsed = ToolPolicySchema.safeParse(JSON.parse(raw));
45
+ if (parsed.success)
46
+ return parsed.data;
47
+ const issue = parsed.error.issues[0];
48
+ throw new ToolPolicyUnreadable(issue?.path.at(-1) === "tool"
49
+ ? `Rule ${Number(issue.path[1]) + 1} of the Tenant's tool policy names a tool by a model-facing name: ${issue.message}. Set the policy again (PUT /v1/tenant/tool-policy)`
50
+ : `The Tenant's tool policy could not be read: ${issue?.message ?? "malformed"}`);
28
51
  }
29
52
  /**
30
53
  * The decision for `request`, a session's MCP or HTTP tool call: the Tenant's rules and the
@@ -35,22 +58,37 @@ export async function toolCallDecision(t, s, request, kind) {
35
58
  try {
36
59
  rules = (await readToolPolicy(t)).rules;
37
60
  }
38
- catch {
39
- return { effect: "deny", reason: "The Tenant's tool policy could not be read, so no tool call runs" };
61
+ catch (error) {
62
+ return {
63
+ effect: "deny",
64
+ reason: error instanceof ToolPolicyUnreadable
65
+ ? `${error.message}, so no tool call runs`
66
+ : "The Tenant's tool policy could not be read, so no tool call runs",
67
+ };
40
68
  }
41
- const mcp = mcpToolOf(s.mcpSnapshot, request);
42
- return decide(rules, {
69
+ return decide(rules, policyCallOf(s, request, kind), pinnedApproval(turnManifestOf(s), request));
70
+ }
71
+ /** `request` as the policy reads it: by its target's identity (protocol 15). */
72
+ export function policyCallOf(s, request, kind) {
73
+ const target = request.target;
74
+ return {
43
75
  kind,
44
- tool: request.toolName ?? request.key ?? request.effectId,
45
- ...(mcp ? { server: mcp.serverName } : {}),
76
+ tool: target?.kind === "mcp" || target?.kind === "http"
77
+ ? target.tool
78
+ : (request.key ?? request.effectId),
79
+ ...(target?.kind === "mcp" ? { server: target.server } : {}),
46
80
  agent: request.agent?.id ?? s.agentId,
47
81
  input: request.input,
48
- }, pinnedApproval(turnManifestOf(s), s.mcpSnapshot, request));
82
+ ...(request.toolName === undefined ? {} : { name: request.toolName }),
83
+ };
49
84
  }
50
85
  /** Decides `call` by `rules`; `pinnedAsk` adds the ask of a manifest saved before R5 step 2. */
51
86
  export function decide(rules, call, pinnedAsk = false) {
52
87
  let strictest = pinnedAsk
53
- ? { effect: "ask", reason: `The agent's manifest asks for approval of '${call.tool}'` }
88
+ ? {
89
+ effect: "ask",
90
+ reason: `The agent's manifest asks for approval of '${call.server === undefined ? call.tool : `${call.server} / ${call.tool}`}'`,
91
+ }
54
92
  : undefined;
55
93
  for (const rule of rules) {
56
94
  if (!matches(rule, call))
@@ -63,12 +101,17 @@ export function decide(rules, call, pinnedAsk = false) {
63
101
  return { effect: "allow" };
64
102
  return { effect: strictest.effect, reason: strictest.reason };
65
103
  }
66
- /** The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2. */
67
- export function pinnedApproval(manifest, snapshot, request) {
68
- const mcp = mcpToolOf(snapshot, request);
69
- if (mcp)
70
- return mcpToolSettingsIn(manifest, mcp).approval === "always";
71
- return declaredHttpTool(manifest, request.agent?.id, request.toolName)?.approval === "always";
104
+ /**
105
+ * The pinned `approval: "always"` of the call's tool, in a manifest saved before R5 step 2: an
106
+ * MCP tool's settings by its own name, an HTTP tool's declaration by its declared name.
107
+ */
108
+ export function pinnedApproval(manifest, request) {
109
+ const target = request.target;
110
+ if (target?.kind === "mcp")
111
+ return mcpToolSettingsIn(manifest, request.agent?.id, target.server, target.tool).approval === "always";
112
+ if (target?.kind === "http")
113
+ return declaredHttpTool(manifest, request.agent?.id, target.tool)?.approval === "always";
114
+ return false;
72
115
  }
73
116
  function matches(rule, call) {
74
117
  if (rule.kind !== undefined && rule.kind !== call.kind)
@@ -0,0 +1,22 @@
1
+ /**
2
+ * A Model Gate over a `ModelProvider` (protocol 15): how a Runtime whose model is scripted (tests,
3
+ * `model: {kind: "scripted"}` or `"fixture"`, an embedder's provider) serves its harness's model
4
+ * calls. The harness calls the gate as it calls any (`invokeModel`), in process or not: no harness
5
+ * calls a provider directly.
6
+ *
7
+ * The provider gets the call as an effect (`HostEffect`), rebuilt from the request: the ids, the
8
+ * agent used as a tool, the call's purpose, and the call as its `input`. With a store, each call
9
+ * is metered in the usage ledger like the Tenant's own gate's, and the Tenant's fixture model
10
+ * setting is answered first (R4.3). It dies with its process: it never `recovers`.
11
+ */
12
+ import type { HostEffect } from "@nylorun/harness/run";
13
+ import type { ModelProvider } from "../core/provider.js";
14
+ import type { SessionStore } from "../store/types.js";
15
+ import type { Logger } from "../tenant/types.js";
16
+ import type { ModelGate, ModelGateRequest } from "./model-gate.js";
17
+ /** The effect a provider sees for `request`. */
18
+ export declare function providerEffect(request: ModelGateRequest): HostEffect;
19
+ export declare function providerModelGate(provider: ModelProvider, options?: {
20
+ readonly store?: SessionStore;
21
+ readonly logger?: Logger;
22
+ }): ModelGate;
@@ -0,0 +1,30 @@
1
+ import { serve } from "./in-process.js";
2
+ import { createMeter } from "./meter.js";
3
+ /** The effect a provider sees for `request`. */
4
+ export function providerEffect(request) {
5
+ return {
6
+ effectId: request.effectId,
7
+ sessionId: request.sessionId,
8
+ turnId: request.turnId,
9
+ agentId: request.agentId,
10
+ // The gate is told no definition hash; a provider routes by the call, never by the hash.
11
+ manifestHash: "",
12
+ kind: "model",
13
+ ...(request.agent === undefined ? {} : { agent: request.agent }),
14
+ ...(request.purpose === undefined ? {} : { purpose: request.purpose }),
15
+ input: request.call,
16
+ context: { invocationId: request.invocationId },
17
+ };
18
+ }
19
+ export function providerModelGate(provider, options = {}) {
20
+ const direct = {
21
+ call: async (request, signal) => (await provider(providerEffect(request), signal)),
22
+ };
23
+ const { store, logger } = options;
24
+ if (!store || !logger)
25
+ return direct;
26
+ const meter = createMeter({ logger });
27
+ return {
28
+ call: (request, signal) => meter.call(store, request, () => serve(store, direct, request, signal)),
29
+ };
30
+ }
@@ -21,8 +21,6 @@ import type { Logger } from "../tenant/types.js";
21
21
  import { type InflightCallsOptions, type InflightOwner } from "./inflight.js";
22
22
  import type { TenantVaults } from "./tenant-vaults.js";
23
23
  import type { McpAnswer } from "./tool-contract.js";
24
- /** How long a settled crossing is kept. */
25
- export declare const TOOL_CROSSING_TTL_MS: number;
26
24
  /** A tool call's answer: an MCP `CallToolResult`, or an HTTP tool's outcome. */
27
25
  export type ToolCallAnswer = McpAnswer<Record<string, unknown>>;
28
26
  export interface ToolCalls {
@@ -40,7 +38,7 @@ export interface ToolCalls {
40
38
  cancel(key: string, sessionId?: string): boolean;
41
39
  /** Aborts every running call (gateway shutdown). */
42
40
  close(): void;
43
- /** Deletes crossings settled more than `TOOL_CROSSING_TTL_MS` ago. */
41
+ /** Deletes crossings settled more than `gates.toolCrossingTtlMs` (Host tuning, 24 h) ago. */
44
42
  prune(): Promise<void>;
45
43
  }
46
44
  export interface ToolCallsOptions {
@@ -1,6 +1,5 @@
1
1
  import { createInflightCalls, InflightConflict, } from "./inflight.js";
2
- /** How long a settled crossing is kept. */
3
- export const TOOL_CROSSING_TTL_MS = 24 * 60 * 60_000;
2
+ import { hostTuning } from "../tuning.js";
4
3
  const CANCELLED = {
5
4
  ok: false,
6
5
  error: { message: "The call was cancelled while it ran; it may have reached its server" },
@@ -71,7 +70,7 @@ export function createToolCalls(options) {
71
70
  async prune() {
72
71
  try {
73
72
  const vault = await vaults.open(undefined);
74
- const before = new Date(now() - TOOL_CROSSING_TTL_MS).toISOString();
73
+ const before = new Date(now() - hostTuning().gates.toolCrossingTtlMs).toISOString();
75
74
  await vault.store.tx((t) => t.pruneToolCrossings(before));
76
75
  }
77
76
  catch {
@@ -21,7 +21,7 @@ export interface HttpToolGateOptions {
21
21
  readonly runTokens?: RunTokens;
22
22
  /** Sent as `Nylorun-Tenant`; the gate checks it against its database's Tenant. */
23
23
  readonly tenantId?: string;
24
- /** How long an MCP request may stay silent. Default `GATE_CLIENT_TIMEOUT_MS`. */
24
+ /** How long an MCP request may stay silent. Default `gates.clientTimeoutMs` (Host tuning). */
25
25
  readonly timeoutMs?: number;
26
26
  }
27
27
  export declare function httpToolGate(options: HttpToolGateOptions): ToolGate;
@@ -26,7 +26,7 @@ import { request as httpRequest } from "node:http";
26
26
  import { request as httpsRequest } from "node:https";
27
27
  import { McpCallFailed, } from "../mcp/connect.js";
28
28
  import { TENANT_HEADER } from "./contract.js";
29
- import { GATE_CLIENT_TIMEOUT_MS } from "./http-client.js";
29
+ import { hostTuning } from "../tuning.js";
30
30
  import { HTTP_CALLS_PATH, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, TOOL_CALLS_PATH, } from "./tool-contract.js";
31
31
  /**
32
32
  * A keyed call whose answer the hop lost (R5.3): the gate never got it, the connection broke,
@@ -36,11 +36,9 @@ import { HTTP_CALLS_PATH, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, TOOL_
36
36
  export class GateCallLost extends Error {
37
37
  name = "GateCallLost";
38
38
  }
39
- /** How long a close or a cancel may take; neither changes an outcome. */
40
- const SHORT_TIMEOUT_MS = 2_000;
41
39
  export function httpToolGate(options) {
42
40
  const where = new URL(options.url).origin;
43
- const timeoutMs = options.timeoutMs ?? GATE_CLIENT_TIMEOUT_MS;
41
+ const timeoutMs = options.timeoutMs ?? hostTuning().gates.clientTimeoutMs;
44
42
  function exchange(path, body, request) {
45
43
  const url = new URL(path, options.url);
46
44
  const send = url.protocol === "https:" ? httpsRequest : httpRequest;
@@ -88,7 +86,7 @@ export function httpToolGate(options) {
88
86
  responded = true;
89
87
  readBody(response).then((text) => settle({ kind: "answer", status: response.statusCode ?? 0, text }), (error) => failed(error));
90
88
  });
91
- outgoing.on("socket", (socket) => socket.setKeepAlive(true, 30_000));
89
+ outgoing.on("socket", (socket) => socket.setKeepAlive(true, hostTuning().gates.keepAliveMs));
92
90
  outgoing.on("finish", () => {
93
91
  sent = true;
94
92
  });
@@ -147,7 +145,8 @@ export function httpToolGate(options) {
147
145
  }
148
146
  async function fireAndForget(path, scope) {
149
147
  await exchange(path, scope.body, {
150
- timeoutMs: SHORT_TIMEOUT_MS,
148
+ // A close or a cancel is short (`gates.shortTimeoutMs`); neither changes an outcome.
149
+ timeoutMs: hostTuning().gates.shortTimeoutMs,
151
150
  ...(scope.bearer ? { bearer: scope.bearer } : {}),
152
151
  });
153
152
  }
@@ -23,14 +23,17 @@
23
23
  * - `POST /nylorun/v1/mcp/close` `{server}`: closes the server's connection; `204`.
24
24
  * Each answers `200 {ok: true, result}` or `200 {ok: false, error}`: an MCP failure is an
25
25
  * answer, not a gate error. A tool call's failure is coded (`error.failure`, R2b C7), and neither
26
- * a result nor a failure carries a credential value the gate sent (C8). An answer past 8 MiB is
27
- * `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; core shapes what fits into a
28
- * result the model can take (`tenant/tool-results.ts`). A keyed call whose earlier attempt was
26
+ * a result nor a failure carries a credential value the gate sent (C8). An answer past the
27
+ * Tenant's Runtime limit `toolAnswerBytes` (default 8 MiB), which also caps the bodies the gate
28
+ * reads, is `mcp.too-large` (C11), as an HTTP tool's is `http.too-large`; the harness shapes
29
+ * what fits into a result the model can take (`shapeToolOutcome`, protocol 15). The tool-calls
30
+ * body names the tool by its identity: the server (`{agentId?, serverName}`) and the server's
31
+ * own name for the tool (`name`). A keyed call whose earlier attempt was
29
32
  * lost with the gateway runs again under the same key (R5.3); one another gateway process holds
30
33
  * answers `{ok: false, error: {uncertain: true}}`, and the caller sends it again.
31
34
  *
32
35
  * HTTP tools (R2 M3). `POST /nylorun/v1/http-calls` `{tool, effectId, turnId, input}`: one call
33
- * of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, toolName}`), or a
36
+ * of an HTTP tool. The loop names the tool (`{sessionId?, agentId?, toolName}`, its declared name), or a
34
37
  * flow's HTTP stage or HTTP verifier by its stage key (`{sessionId?, stage}`); the gate finds its
35
38
  * URL, method and credential in the session's pinned manifest
36
39
  * (`gates/http-tool.ts`). A run token's turn is the call's; `turnId` counts only under core's
@@ -39,7 +42,7 @@
39
42
  * model sees), or `{ok: false, error: {uncertain: true}}` for a call another gateway process holds.
40
43
  */
41
44
  import type { McpCallFailure } from "../mcp/connect.js";
42
- export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, type HttpCallBody, type ToolCallBody, } from "@nylorun/core/harness-api";
45
+ export { HTTP_CALLS_PATH, HttpCallBodySchema, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, type HttpCallBody, type ToolCallBody, } from "@nylorun/core/harness-api";
43
46
  /** Why an MCP request failed, as the loop's diagnostics read it (`mcp/connect.ts`). */
44
47
  export interface McpGateError {
45
48
  readonly message: string;
@@ -1 +1 @@
1
- export { HTTP_CALLS_PATH, HttpCallBodySchema, MAX_TOOL_BODY_BYTES, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "@nylorun/core/harness-api";
1
+ export { HTTP_CALLS_PATH, HttpCallBodySchema, MCP_CLOSE_PATH, MCP_CONNECT_PATH, MCP_LIST_PATH, McpListBodySchema, McpServerBodySchema, McpServerRefSchema, TOOL_CALLS_PATH, ToolCallBodySchema, } from "@nylorun/core/harness-api";
@@ -6,17 +6,20 @@
6
6
  *
7
7
  * Two implementations: `inProcessToolGate` (embedding, the ephemeral Runtime, tests), which
8
8
  * opens remote servers and calls HTTP tools in this process, and the HTTP client of the gates
9
- * service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it.
9
+ * service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it. A
10
+ * harness's MCP pool opens every server through a gate (`openMcp`), in process too (protocol
11
+ * 15): the vault authorizes a connection only here.
10
12
  */
11
- import type { LiveConnection } from "../mcp/connect.js";
12
- import type { McpServerRef } from "../mcp/pool.js";
13
+ import { openMcpServer, type LiveConnection } from "../mcp/connect.js";
14
+ import { type McpServerRef } from "../mcp/pool.js";
13
15
  import type { OutboundPolicy } from "../tenant/outbound.js";
14
16
  import type { Logger } from "../tenant/types.js";
15
17
  import { type HttpOutcome, type HttpToolCall, type HttpToolTenant } from "./http-tool.js";
16
18
  export interface ToolGate {
17
19
  /**
18
- * Opens a remote (`streamable-http` or `sse`) MCP server of a session. Absent: the loop's
19
- * pool opens it itself, with the Tenant vault.
20
+ * Opens a remote (`streamable-http` or `sse`) MCP server of a session: the one its pinned
21
+ * manifest declares, authorized with the Tenant vault. Absent: this Runtime opens no MCP
22
+ * server.
20
23
  */
21
24
  openMcp?(server: McpServerRef): Promise<LiveConnection>;
22
25
  /**
@@ -39,9 +42,17 @@ export interface ToolGate {
39
42
  }): Promise<void>;
40
43
  }
41
44
  /**
42
- * The Tool Gate in the caller's process: remote MCP through the pool, and HTTP tools with
43
- * `tenant`'s sessions and vault.
45
+ * The Tool Gate in the caller's process: remote MCP servers and HTTP tools with `tenant`'s
46
+ * sessions and vault. `openMcp` finds the server in the session's pinned manifest (as the gates
47
+ * service does), so a caller names a server, never a URL or a credential. `open` replaces how a
48
+ * server is opened (tests).
44
49
  */
45
50
  export declare function inProcessToolGate(policy?: OutboundPolicy, tenant?: HttpToolTenant & {
46
51
  readonly logger?: Logger;
52
+ readonly open?: typeof openMcpServer;
47
53
  }): ToolGate;
54
+ /**
55
+ * How a harness's MCP pool opens a server: through `gate`, or, for a Runtime whose gate opens
56
+ * none, never (the call fails to the model as `mcp.unreachable`).
57
+ */
58
+ export declare function openMcpThrough(gate: Pick<ToolGate, "openMcp">): (server: McpServerRef) => Promise<LiveConnection>;
@@ -1,14 +1,56 @@
1
+ /**
2
+ * The Tool Gate seam (blueprint §12, F4.1). Every tool call that leaves the loop crosses one
3
+ * `ToolGate`: a remote MCP server's connection and calls, and an HTTP tool's request (R2 M3).
4
+ * The gate owns what those need that the loop must not hold: the MCP and HTTP tool credentials,
5
+ * and the outbound route.
6
+ *
7
+ * Two implementations: `inProcessToolGate` (embedding, the ephemeral Runtime, tests), which
8
+ * opens remote servers and calls HTTP tools in this process, and the HTTP client of the gates
9
+ * service (`tool-client.ts`, the `gateway` container). The sandbox tools never cross it. A
10
+ * harness's MCP pool opens every server through a gate (`openMcp`), in process too (protocol
11
+ * 15): the vault authorizes a connection only here.
12
+ */
13
+ import { openMcpServer } from "../mcp/connect.js";
14
+ import { findServer } from "../mcp/pool.js";
1
15
  import { runHttpTool } from "./http-tool.js";
2
16
  /**
3
- * The Tool Gate in the caller's process: remote MCP through the pool, and HTTP tools with
4
- * `tenant`'s sessions and vault.
17
+ * The Tool Gate in the caller's process: remote MCP servers and HTTP tools with `tenant`'s
18
+ * sessions and vault. `openMcp` finds the server in the session's pinned manifest (as the gates
19
+ * service does), so a caller names a server, never a URL or a credential. `open` replaces how a
20
+ * server is opened (tests).
5
21
  */
6
22
  export function inProcessToolGate(policy = {}, tenant) {
23
+ if (!tenant)
24
+ return {};
25
+ const open = tenant.open ?? openMcpServer;
7
26
  return {
8
- ...(tenant
9
- ? {
10
- callHttp: (call, signal) => runHttpTool(tenant, call, { policy, signal, ...(tenant.logger ? { logger: tenant.logger } : {}) }),
11
- }
12
- : {}),
27
+ callHttp: (call, signal) => runHttpTool(tenant, call, { policy, signal, ...(tenant.logger ? { logger: tenant.logger } : {}) }),
28
+ async openMcp(server) {
29
+ const session = await tenant.session(server.sessionId);
30
+ if (!session)
31
+ throw new Error(`Session ${server.sessionId} not found`);
32
+ const declared = findServer(session.manifest, server.agentId, server.serverName);
33
+ if (!declared)
34
+ throw new Error(`MCP server '${server.serverName}' is not declared`);
35
+ return open({
36
+ server: declared.server,
37
+ policy,
38
+ errorExcerptChars: (await tenant.limits()).errorExcerptChars,
39
+ authorize: (url) => tenant.authorize(server.sessionId, {
40
+ url,
41
+ serverName: server.serverName,
42
+ ...(server.agentId === undefined ? {} : { agentId: server.agentId }),
43
+ }),
44
+ });
45
+ },
13
46
  };
14
47
  }
48
+ /**
49
+ * How a harness's MCP pool opens a server: through `gate`, or, for a Runtime whose gate opens
50
+ * none, never (the call fails to the model as `mcp.unreachable`).
51
+ */
52
+ export function openMcpThrough(gate) {
53
+ return (server) => gate.openMcp
54
+ ? gate.openMcp(server)
55
+ : Promise.reject(new Error("This Runtime opens MCP servers only through a Tool Gate, and has none"));
56
+ }