@nylorun/runtime 0.23.0-beta → 0.25.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 (301) hide show
  1. package/CHANGELOG.md +252 -0
  2. package/README.md +87 -28
  3. package/dist/adapters/execution/restate.js +1 -1
  4. package/dist/adapters/replication/pgoutput.d.ts +2 -0
  5. package/dist/adapters/replication/pgoutput.js +6 -3
  6. package/dist/adapters/sandbox/local.d.ts +9 -1
  7. package/dist/adapters/sandbox/local.js +16 -4
  8. package/dist/adapters/sandbox/virtual.js +4 -1
  9. package/dist/api/a2a/card.d.ts +11 -1
  10. package/dist/api/a2a/card.js +26 -2
  11. package/dist/api/a2a/endpoint.js +9 -3
  12. package/dist/api/a2a/protocol.d.ts +25 -2
  13. package/dist/api/a2a/protocol.js +20 -3
  14. package/dist/api/a2a/routes.d.ts +6 -1
  15. package/dist/api/a2a/routes.js +67 -9
  16. package/dist/api/a2a/tasks.js +2 -1
  17. package/dist/api/ag-ui/endpoint.js +8 -3
  18. package/dist/api/ag-ui/history.d.ts +6 -1
  19. package/dist/api/ag-ui/history.js +39 -3
  20. package/dist/api/ag-ui/routes.d.ts +4 -1
  21. package/dist/api/ag-ui/routes.js +63 -20
  22. package/dist/api/ag-ui/translate.js +3 -2
  23. package/dist/api/components.d.ts +5327 -1091
  24. package/dist/api/components.js +40 -16
  25. package/dist/api/gate/keys.d.ts +2 -1
  26. package/dist/api/gate/keys.js +14 -1
  27. package/dist/api/gate/routes.d.ts +6 -0
  28. package/dist/api/gate/routes.js +52 -3
  29. package/dist/api/http/app.js +17 -6
  30. package/dist/api/http/body.d.ts +3 -3
  31. package/dist/api/http/body.js +7 -7
  32. package/dist/api/http/define.d.ts +6 -0
  33. package/dist/api/http/define.js +13 -1
  34. package/dist/api/http/respond.js +1 -1
  35. package/dist/api/http/routes/artifacts.js +35 -8
  36. package/dist/api/http/routes/coverage.d.ts +9 -0
  37. package/dist/api/http/routes/coverage.js +34 -0
  38. package/dist/api/http/routes/environment-manifests.d.ts +9 -0
  39. package/dist/api/http/routes/environment-manifests.js +118 -0
  40. package/dist/api/http/routes/environments.d.ts +11 -0
  41. package/dist/api/http/routes/environments.js +332 -0
  42. package/dist/api/http/routes/mcp.d.ts +1 -1
  43. package/dist/api/http/routes/mcp.js +2 -2
  44. package/dist/api/http/routes/me.js +1 -1
  45. package/dist/api/http/routes/reads.d.ts +94 -9
  46. package/dist/api/http/routes/reads.js +54 -5
  47. package/dist/api/http/routes/sessions.js +24 -9
  48. package/dist/api/http/routes/tenant.js +65 -15
  49. package/dist/api/inbound-files.d.ts +65 -0
  50. package/dist/api/inbound-files.js +115 -0
  51. package/dist/api/openapi.js +48 -28
  52. package/dist/artifacts/disk.d.ts +59 -0
  53. package/dist/artifacts/disk.js +569 -0
  54. package/dist/artifacts/export.d.ts +1 -1
  55. package/dist/artifacts/export.js +2 -3
  56. package/dist/artifacts/folders.d.ts +6 -3
  57. package/dist/artifacts/folders.js +17 -12
  58. package/dist/artifacts/media-types.d.ts +15 -0
  59. package/dist/artifacts/media-types.js +56 -4
  60. package/dist/artifacts/parts.d.ts +12 -3
  61. package/dist/artifacts/parts.js +15 -4
  62. package/dist/artifacts/service.d.ts +2 -1
  63. package/dist/artifacts/service.js +6 -1
  64. package/dist/artifacts/workspace.d.ts +5 -5
  65. package/dist/artifacts/workspace.js +7 -19
  66. package/dist/core/flow-host.d.ts +3 -4
  67. package/dist/core/flow-host.js +5 -2
  68. package/dist/core/provider.d.ts +3 -2
  69. package/dist/core/provider.js +5 -37
  70. package/dist/core/sandbox-routes.d.ts +2 -21
  71. package/dist/core/sandbox-routes.js +11 -44
  72. package/dist/core/turn-manifest.d.ts +3 -2
  73. package/dist/core/turn-manifest.js +29 -5
  74. package/dist/execution/types.d.ts +1 -1
  75. package/dist/gates/contract.d.ts +3 -24
  76. package/dist/gates/contract.js +1 -36
  77. package/dist/gates/egress-credentials.d.ts +9 -0
  78. package/dist/gates/egress-credentials.js +11 -4
  79. package/dist/gates/egress.d.ts +22 -1
  80. package/dist/gates/egress.js +116 -10
  81. package/dist/gates/fixture-model.d.ts +28 -0
  82. package/dist/gates/fixture-model.js +92 -0
  83. package/dist/gates/handler.js +4 -4
  84. package/dist/gates/http-tool.d.ts +9 -6
  85. package/dist/gates/http-tool.js +11 -17
  86. package/dist/gates/in-process.d.ts +5 -0
  87. package/dist/gates/in-process.js +12 -1
  88. package/dist/gates/journal.d.ts +30 -0
  89. package/dist/gates/journal.js +51 -0
  90. package/dist/gates/mcp-handler.d.ts +4 -0
  91. package/dist/gates/mcp-handler.js +7 -4
  92. package/dist/gates/meter.d.ts +10 -2
  93. package/dist/gates/meter.js +20 -5
  94. package/dist/gates/policy.d.ts +49 -0
  95. package/dist/gates/policy.js +132 -0
  96. package/dist/gates/tenant-vaults.d.ts +7 -7
  97. package/dist/gates/tenant-vaults.js +5 -5
  98. package/dist/gates/tool-calls.d.ts +10 -5
  99. package/dist/gates/tool-calls.js +28 -10
  100. package/dist/gates/tool-client.d.ts +8 -0
  101. package/dist/gates/tool-client.js +27 -11
  102. package/dist/gates/tool-contract.d.ts +11 -69
  103. package/dist/gates/tool-contract.js +1 -85
  104. package/dist/gates/tool-gate.d.ts +1 -1
  105. package/dist/harness/calls.d.ts +14 -7
  106. package/dist/harness/calls.js +30 -24
  107. package/dist/harness/executors.js +12 -9
  108. package/dist/harness/main.js +24 -5
  109. package/dist/harness/pod.d.ts +39 -0
  110. package/dist/harness/pod.js +46 -0
  111. package/dist/harness/service.d.ts +0 -1
  112. package/dist/harness/service.js +17 -9
  113. package/dist/harness/setup.d.ts +29 -0
  114. package/dist/harness/setup.js +174 -0
  115. package/dist/harness/ws-client.js +3 -2
  116. package/dist/harness/ws-port.d.ts +1 -5
  117. package/dist/harness/ws-port.js +1 -5
  118. package/dist/harness-api/in-process.js +3 -3
  119. package/dist/harness-api/record.d.ts +15 -1
  120. package/dist/harness-api/record.js +180 -50
  121. package/dist/harness-api/redact.d.ts +3 -0
  122. package/dist/harness-api/redact.js +45 -0
  123. package/dist/harness-api/server.d.ts +35 -4
  124. package/dist/harness-api/server.js +130 -17
  125. package/dist/harness-api/start.d.ts +10 -3
  126. package/dist/harness-api/start.js +40 -10
  127. package/dist/harness-api/tool-journal.d.ts +105 -0
  128. package/dist/harness-api/tool-journal.js +132 -0
  129. package/dist/harness-api/tool-status.d.ts +13 -0
  130. package/dist/harness-api/tool-status.js +26 -0
  131. package/dist/harness-api/workspace.d.ts +12 -7
  132. package/dist/harness-api/workspace.js +47 -25
  133. package/dist/harness-api/ws-server.d.ts +11 -3
  134. package/dist/harness-api/ws-server.js +94 -15
  135. package/dist/host/gates.d.ts +2 -1
  136. package/dist/host/gates.js +10 -2
  137. package/dist/host/main.js +54 -5
  138. package/dist/host/operate.js +67 -5
  139. package/dist/host/stack-config.d.ts +64 -0
  140. package/dist/host/stack-config.js +62 -2
  141. package/dist/keys/client.js +4 -1
  142. package/dist/keys/keys.d.ts +13 -6
  143. package/dist/keys/keys.js +9 -8
  144. package/dist/management-openapi.json +1264 -2171
  145. package/dist/mcp/connect.d.ts +26 -22
  146. package/dist/mcp/connect.js +30 -34
  147. package/dist/mcp/pool.d.ts +3 -6
  148. package/dist/mcp/pool.js +14 -28
  149. package/dist/mcp/preview.d.ts +3 -3
  150. package/dist/mcp/preview.js +10 -21
  151. package/dist/mcp/snapshot.d.ts +16 -24
  152. package/dist/mcp/snapshot.js +33 -45
  153. package/dist/model/capabilities.d.ts +26 -0
  154. package/dist/model/capabilities.js +44 -0
  155. package/dist/model/catalog.d.ts +34 -3
  156. package/dist/model/catalog.js +38 -2
  157. package/dist/model/models.d.ts +2 -0
  158. package/dist/model/models.js +1 -1
  159. package/dist/model/native-blocks.d.ts +35 -0
  160. package/dist/model/native-blocks.js +170 -0
  161. package/dist/model/pi-model.js +114 -94
  162. package/dist/openapi.json +12822 -3943
  163. package/dist/reads/types.d.ts +16 -3
  164. package/dist/record/append.d.ts +3 -1
  165. package/dist/record/append.js +1 -0
  166. package/dist/record/envelope.d.ts +5 -1
  167. package/dist/record/envelope.js +7 -3
  168. package/dist/record/environment.d.ts +25 -0
  169. package/dist/record/environment.js +44 -0
  170. package/dist/record/index.d.ts +1 -1
  171. package/dist/record/index.js +1 -1
  172. package/dist/sandbox/disk-fs.d.ts +95 -0
  173. package/dist/sandbox/disk-fs.js +206 -0
  174. package/dist/sandbox/join.d.ts +17 -18
  175. package/dist/sandbox/join.js +46 -15
  176. package/dist/sandbox/manager.d.ts +47 -21
  177. package/dist/sandbox/manager.js +162 -107
  178. package/dist/sandbox/placement.d.ts +14 -13
  179. package/dist/sandbox/placement.js +31 -14
  180. package/dist/sandbox/pods/client.d.ts +9 -2
  181. package/dist/sandbox/pods/lifecycle.d.ts +36 -27
  182. package/dist/sandbox/pods/lifecycle.js +29 -26
  183. package/dist/sandbox/pods/reconcile.js +18 -12
  184. package/dist/sandbox/pods/spec.d.ts +15 -13
  185. package/dist/sandbox/pods/spec.js +17 -20
  186. package/dist/sandbox/records.d.ts +2 -2
  187. package/dist/sandbox/records.js +3 -3
  188. package/dist/sandbox/resolve.d.ts +37 -27
  189. package/dist/sandbox/resolve.js +73 -41
  190. package/dist/sandbox/runtime-tools.d.ts +9 -0
  191. package/dist/sandbox/runtime-tools.js +16 -0
  192. package/dist/sandbox/session-sandbox.d.ts +4 -6
  193. package/dist/sandbox/session-sandbox.js +47 -94
  194. package/dist/sandbox/setup.d.ts +64 -0
  195. package/dist/sandbox/setup.js +160 -0
  196. package/dist/sandbox/share.d.ts +21 -26
  197. package/dist/sandbox/share.js +36 -63
  198. package/dist/sandbox/skills.d.ts +8 -6
  199. package/dist/sandbox/skills.js +24 -23
  200. package/dist/sandbox/tenant-config.d.ts +12 -14
  201. package/dist/sandbox/tenant-config.js +12 -17
  202. package/dist/sandbox/types.d.ts +6 -0
  203. package/dist/store/postgres/drizzle/0020_environments.sql +63 -0
  204. package/dist/store/postgres/drizzle/0021_disk_versions.sql +7 -0
  205. package/dist/store/postgres/drizzle/0022_model_usage_timing.sql +5 -0
  206. package/dist/store/postgres/drizzle/0023_artifact_pages.sql +1 -0
  207. package/dist/store/postgres/drizzle/0024_flat_manifest.sql +7 -0
  208. package/dist/store/postgres/drizzle/0025_environment_manifests.sql +14 -0
  209. package/dist/store/postgres/drizzle/0026_environment_forks.sql +1 -0
  210. package/dist/store/postgres/drizzle/0027_environment_image_digest.sql +1 -0
  211. package/dist/store/postgres/drizzle/meta/_journal.json +56 -0
  212. package/dist/store/postgres/reads.js +123 -17
  213. package/dist/store/postgres/record-writer.d.ts +4 -4
  214. package/dist/store/postgres/record-writer.js +9 -9
  215. package/dist/store/postgres/record.d.ts +7 -0
  216. package/dist/store/postgres/record.js +23 -2
  217. package/dist/store/postgres/schema.d.ts +1126 -736
  218. package/dist/store/postgres/schema.js +136 -73
  219. package/dist/store/postgres/store.js +195 -67
  220. package/dist/store/types.d.ts +165 -50
  221. package/dist/tenant/advance.d.ts +7 -6
  222. package/dist/tenant/advance.js +118 -42
  223. package/dist/tenant/artifact-tool.d.ts +1 -1
  224. package/dist/tenant/artifact-tool.js +46 -26
  225. package/dist/tenant/commands.d.ts +15 -2
  226. package/dist/tenant/commands.js +115 -14
  227. package/dist/tenant/context.d.ts +56 -24
  228. package/dist/tenant/context.js +4 -15
  229. package/dist/tenant/effects.d.ts +1 -1
  230. package/dist/tenant/effects.js +44 -29
  231. package/dist/tenant/environment-manifests.d.ts +57 -0
  232. package/dist/tenant/environment-manifests.js +200 -0
  233. package/dist/tenant/environments.d.ts +174 -0
  234. package/dist/tenant/environments.js +1044 -0
  235. package/dist/tenant/harness-tokens.d.ts +21 -0
  236. package/dist/tenant/harness-tokens.js +61 -0
  237. package/dist/tenant/host-token.d.ts +1 -1
  238. package/dist/tenant/host-token.js +2 -4
  239. package/dist/tenant/identity-file.d.ts +4 -4
  240. package/dist/tenant/identity-file.js +17 -8
  241. package/dist/tenant/issuers.js +3 -3
  242. package/dist/tenant/model-setting.d.ts +14 -0
  243. package/dist/tenant/model-setting.js +51 -0
  244. package/dist/tenant/principals.js +1 -1
  245. package/dist/tenant/reset.js +3 -3
  246. package/dist/tenant/runtime.d.ts +6 -1
  247. package/dist/tenant/runtime.js +7 -1
  248. package/dist/tenant/session.d.ts +3 -3
  249. package/dist/tenant/session.js +0 -14
  250. package/dist/tenant/sessions.d.ts +18 -14
  251. package/dist/tenant/sessions.js +99 -143
  252. package/dist/tenant/signing-keys.d.ts +5 -1
  253. package/dist/tenant/skill-tool.d.ts +3 -3
  254. package/dist/tenant/skill-tool.js +6 -11
  255. package/dist/tenant/status.js +3 -3
  256. package/dist/tenant/sweep.js +13 -7
  257. package/dist/tenant/tool-results.js +1 -1
  258. package/dist/tenant/tool-search.d.ts +7 -11
  259. package/dist/tenant/tool-search.js +8 -5
  260. package/dist/tenant/transcript.d.ts +245 -36
  261. package/dist/tenant/transcript.js +64 -12
  262. package/dist/tenant/turn-timing.d.ts +43 -0
  263. package/dist/tenant/turn-timing.js +39 -0
  264. package/dist/tenant/types.d.ts +2 -0
  265. package/dist/traces/ids.d.ts +41 -0
  266. package/dist/traces/ids.js +81 -0
  267. package/dist/traces/key.d.ts +6 -0
  268. package/dist/traces/key.js +37 -0
  269. package/dist/traces/map.d.ts +113 -0
  270. package/dist/traces/map.js +289 -0
  271. package/dist/traces/options.d.ts +7 -0
  272. package/dist/traces/options.js +1 -0
  273. package/dist/traces/otlp.d.ts +42 -0
  274. package/dist/traces/otlp.js +84 -0
  275. package/dist/traces/projector.d.ts +58 -0
  276. package/dist/traces/projector.js +200 -0
  277. package/dist/traces/read.d.ts +23 -0
  278. package/dist/traces/read.js +16 -0
  279. package/dist/vault/coverage.d.ts +26 -0
  280. package/dist/vault/coverage.js +62 -0
  281. package/dist/vault/error.d.ts +11 -1
  282. package/dist/vault/error.js +5 -1
  283. package/dist/vault/host-model.d.ts +4 -1
  284. package/dist/vault/host-model.js +10 -2
  285. package/dist/vault/service.d.ts +92 -48
  286. package/dist/vault/service.js +462 -376
  287. package/dist/vault/sources.d.ts +5 -16
  288. package/dist/vault/sources.js +6 -8
  289. package/dist/version.d.ts +1 -1
  290. package/dist/version.js +2 -2
  291. package/package.json +13 -13
  292. package/dist/api/http/routes/sandboxes.d.ts +0 -9
  293. package/dist/api/http/routes/sandboxes.js +0 -153
  294. package/dist/api/http/routes/vaults.d.ts +0 -10
  295. package/dist/api/http/routes/vaults.js +0 -139
  296. package/dist/record/sandbox.d.ts +0 -25
  297. package/dist/record/sandbox.js +0 -43
  298. package/dist/sandbox/capability.d.ts +0 -3
  299. package/dist/sandbox/capability.js +0 -10
  300. package/dist/tenant/sandboxes.d.ts +0 -123
  301. package/dist/tenant/sandboxes.js +0 -566
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { SESSION_STATUSES } from "@nylorun/core/contracts";
3
- import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, } from "../../components.js";
3
+ import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, ToolCallsPage, TurnTrace, } from "../../components.js";
4
+ import { turnTrace } from "../../../traces/read.js";
4
5
  import { readAccess, readStoreOf } from "../../../reads/access.js";
5
6
  import { requireApplication, requirePrincipal } from "../../../tenant/auth.js";
6
7
  import { fail } from "../../../tenant/http.js";
7
- import { sandboxGrantsOf } from "../../../tenant/sandboxes.js";
8
+ import { environmentGrantsOf } from "../../../tenant/environments.js";
8
9
  import { tenantRoute } from "../define.js";
9
10
  import { jsonResponse } from "../respond.js";
10
11
  export const pageQuery = z.object({
@@ -15,7 +16,7 @@ export const sessionPageQuery = pageQuery
15
16
  .extend({
16
17
  agentId: z.string().optional(),
17
18
  status: z.enum(SESSION_STATUSES).optional(),
18
- sandboxId: z.string().optional(),
19
+ environmentId: z.string().optional(),
19
20
  ownerUserId: z.string().optional(),
20
21
  })
21
22
  .strict();
@@ -31,11 +32,11 @@ export async function sessionPage(ctx, scope, query) {
31
32
  requireApplication(scope);
32
33
  return readStoreOf(ctx).sessions(filters, { limit, cursor }, readAccess(scope));
33
34
  }
34
- export async function sandboxPage(ctx, scope, query, labels) {
35
+ export async function environmentPage(ctx, scope, query, labels) {
35
36
  const { limit, cursor } = parseQuery(pageQuery.extend({ label: z.string().optional() }).strict(), query);
36
37
  if (limit === undefined)
37
38
  return fail(400, "Pagination requires limit");
38
- return readStoreOf(ctx).sandboxes(labels, { limit, cursor }, readAccess(scope), sandboxGrantsOf(scope));
39
+ return readStoreOf(ctx).environments(labels, { limit, cursor }, readAccess(scope), environmentGrantsOf(scope));
39
40
  }
40
41
  export function readRoutes(api) {
41
42
  const sessionId = z.object({ sessionId: z.string() });
@@ -101,6 +102,54 @@ export function readRoutes(api) {
101
102
  return jsonResponse(200, await store.modelCalls(id, q.turnId, q, access));
102
103
  });
103
104
  }
105
+ const toolCallsQuery = turnQuery
106
+ .extend({
107
+ limit: z.coerce.number().int().min(1).max(200).default(50),
108
+ cursor: z.string().optional(),
109
+ })
110
+ .strict();
111
+ // What ran, with what input, how many sends and how it ended (R5.2): the effect journal's tool
112
+ // rows, readable as the model calls are.
113
+ tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
114
+ method: "get",
115
+ path: "/v1/sessions/{sessionId}/calls/tool",
116
+ tags: ["Sessions"],
117
+ summary: "Page a session's tool calls",
118
+ request: { params: sessionId, query: toolCallsQuery },
119
+ responses: {
120
+ 200: {
121
+ description: "Each tool call in the order it was first sent, with its status and sends",
122
+ content: { "application/json": { schema: ToolCallsPage } },
123
+ },
124
+ },
125
+ }, async (c) => {
126
+ requirePrincipal(c.get("scope"));
127
+ const q = parseQuery(toolCallsQuery, c.req.query());
128
+ return jsonResponse(200, await readStoreOf(c.env.tenant).toolCalls(c.req.param("sessionId"), q.turnId, q, readAccess(c.get("scope"))));
129
+ });
130
+ // A turn's spans (R7.5): the same mapping the trace projector posts, from the turn's log rows,
131
+ // with the installation's content opt-ins. Spans carry cost: the application's, as /calls are.
132
+ tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
133
+ method: "get",
134
+ path: "/v1/sessions/{sessionId}/turns/{turnId}/trace",
135
+ tags: ["Sessions"],
136
+ summary: "Read a turn's trace",
137
+ description: "The turn's spans as OTLP/HTTP JSON (`resourceSpans`), as the Runtime posts them to an OTLP collector: a root span for the turn, a span for each model, tool and egress crossing, ids derived so every read agrees. Post it to a collector's `/v1/traces` to backfill. A turn still running has no root span yet; a turn the session never had has no spans.",
138
+ request: { params: sessionId.extend({ turnId: z.string() }) },
139
+ responses: {
140
+ 200: {
141
+ description: "The turn's spans",
142
+ content: { "application/json": { schema: TurnTrace } },
143
+ },
144
+ },
145
+ }, async (c) => {
146
+ requirePrincipal(c.get("scope"));
147
+ const ctx = c.env.tenant;
148
+ const id = c.req.param("sessionId");
149
+ const turnId = c.req.param("turnId");
150
+ const events = await readStoreOf(ctx).turnEvents(id, turnId, readAccess(c.get("scope")));
151
+ return jsonResponse(200, await turnTrace(ctx, id, turnId, events));
152
+ });
104
153
  const exportQuery = z
105
154
  .object({
106
155
  after: z.string().optional(),
@@ -2,16 +2,17 @@ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
2
  import { z } from "zod";
3
3
  import { PutAgentRequestSchema, PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
4
4
  import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
5
+ import { traceContextOf } from "../../../traces/ids.js";
5
6
  import { AcceptedResponse, ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
6
7
  import { accessOf, requireApplication } from "../../../tenant/auth.js";
7
8
  import { command } from "../../../tenant/commands.js";
8
9
  import { loadSession, sessionOf } from "../../../tenant/context.js";
9
10
  import { fail } from "../../../tenant/http.js";
10
- import { sandboxGrantsOf, sandboxRouteDeps } from "../../../tenant/sandboxes.js";
11
+ import { environmentGrantsOf, sandboxRouteDeps } from "../../../tenant/environments.js";
11
12
  import { readHistory, requestCursor, streamHolderOf, streamSessionEvents } from "../../../tenant/session-streams.js";
12
13
  import { listAgentsPublic, listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "../../../tenant/sessions.js";
13
14
  import { readJson } from "../body.js";
14
- import { tenantRoute } from "../define.js";
15
+ import { TraceHeaders, tenantRoute } from "../define.js";
15
16
  import { pageQuery, sessionPage, sessionPageQuery, parseQuery } from "./reads.js";
16
17
  import { jsonResponse } from "../respond.js";
17
18
  const OWN_SESSIONS = {
@@ -52,12 +53,19 @@ export function sessionRoutes(api) {
52
53
  tags: ["Sessions"],
53
54
  summary: "Send a command to a session",
54
55
  description: "A message starts a turn; the others answer what the session waits on, or cancel. Idempotent on the command's `idempotencyKey`.",
55
- request: { params: sessionId, body: body(SessionCommand) },
56
+ request: { params: sessionId, body: body(SessionCommand), headers: TraceHeaders },
56
57
  responses: {
57
58
  200: json(AcceptedResponse, "The command, accepted"),
58
59
  409: { description: "A turn is running, or the session does not wait on this" },
60
+ 422: {
61
+ description: "A message names a file the Tenant's model does not read (`attachment_unsupported`; `details.parts` lists each with its reason). No turn starts.",
62
+ },
59
63
  },
60
- }, async (c) => jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c.req.raw)), c.get("scope"))));
64
+ }, async (c) => {
65
+ // A `traceparent` joins the turn to the caller's trace (R7.4); a malformed one is ignored.
66
+ const trace = traceContextOf(c.req.raw.headers);
67
+ return jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c.req.raw)), c.get("scope"), trace ? { trace } : {}));
68
+ });
61
69
  tenantRoute(api, {
62
70
  credentials: ["application", "subject", "token"],
63
71
  scopes: ["agents:read", "agents:write"],
@@ -82,10 +90,17 @@ export function sessionRoutes(api) {
82
90
  path: "/v1/agents/{agentId}",
83
91
  tags: ["Agents"],
84
92
  summary: "Put an agent's definition",
85
- request: { params: z.object({ agentId: z.string() }), body: body(PutAgentRequest) },
86
- responses: { 200: json(PutAgentResponse, "The definition, stored") },
93
+ description: "Saves the agent's definition. With `?dryRun=true` (protocol 14) every check runs and nothing is saved: the answer says what a save would record.",
94
+ request: {
95
+ params: z.object({ agentId: z.string() }),
96
+ query: z.object({
97
+ dryRun: z.enum(["true", "false"]).optional().meta({ description: "`true`: check and save nothing" }),
98
+ }),
99
+ body: body(PutAgentRequest),
100
+ },
101
+ responses: { 200: json(PutAgentResponse, "The definition, stored (or checked, with dryRun)") },
87
102
  }, async (c) => {
88
- return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw))));
103
+ return jsonResponse(200, await putDefinition(c.env.tenant, c.req.param("agentId"), PutAgentRequestSchema.parse(await readJson(c.req.raw)), { dryRun: c.req.query("dryRun") === "true" }));
89
104
  });
90
105
  tenantRoute(api, OWN_SESSIONS, {
91
106
  method: "get",
@@ -119,8 +134,8 @@ export function sessionRoutes(api) {
119
134
  // Agent code may trust `info`: only an app server sets it.
120
135
  if (scope.kind === "token" && request.info !== undefined)
121
136
  fail(403, "A token caller cannot set session info", { code: "scope_required" });
122
- const grants = sandboxGrantsOf(scope);
123
- const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : { sandboxGrants: grants });
137
+ const grants = environmentGrantsOf(scope);
138
+ const session = await putSession(ctx, c.req.param("sessionId"), request, accessOf(scope), grants === undefined ? {} : { environmentGrants: grants });
124
139
  return jsonResponse(200, await ctx.store.tx((t) => sessionView(t, session)));
125
140
  });
126
141
  tenantRoute(api, OWN_SESSIONS, {
@@ -1,13 +1,15 @@
1
1
  import { z } from "zod";
2
- import { ModelUsageQuerySchema, PutModelBudgetsRequestSchema, PutHostModelRequestSchema, PutTenantSandboxRequestSchema, ResetTenantRequestSchema, SeedTenantConfigRequestSchema, SelectHostModelRequestSchema, } from "@nylorun/core/contracts";
3
- import { HostModelCatalog, HostModelView, ListProvidersResponse, ModelBudgets, ModelUsageTotals, PutModelBudgetsRequest, PutHostModelRequest, PutTenantSandboxRequest, ResetTenantRequest, ResetTenantResponse, SeedTenantConfigRequest, SeedTenantConfigResponse, SelectHostModelRequest, TenantSandboxView, TenantStatus, } from "../../components.js";
2
+ import { ModelUsageQuerySchema, PutModelBudgetsRequestSchema, PutToolPolicyRequestSchema, PutModelInputsRequestSchema, PutHostModelRequestSchema, PutTenantEnvironmentsRequestSchema, ResetTenantRequestSchema, SeedTenantConfigRequestSchema, SelectHostModelRequestSchema, } from "@nylorun/core/contracts";
3
+ import { HostModelCatalog, HostModelView, ListProvidersResponse, ModelBudgets, ModelInputsView, ModelUsageTotals, PutModelBudgetsRequest, PutToolPolicyRequest, PutModelInputsRequest, ToolPolicy, PutHostModelRequest, PutTenantEnvironmentsRequest, ResetTenantRequest, ResetTenantResponse, SeedTenantConfigRequest, SeedTenantConfigResponse, SelectHostModelRequest, TenantSandboxView, TenantStatus, } from "../../components.js";
4
4
  import { periodStart } from "../../../gates/meter.js";
5
- import { hostModelCatalog } from "../../../model/catalog.js";
5
+ import { readToolPolicy, TOOL_POLICY_SETTING } from "../../../gates/policy.js";
6
+ import { addableInputs, capabilitiesOf, inputsError } from "../../../model/capabilities.js";
7
+ import { catalogModel, hostModelCatalog } from "../../../model/catalog.js";
6
8
  import { sandboxConfigErrors } from "../../../sandbox/resolve.js";
7
9
  import { effectiveSandboxConfig, readSandboxConfig, writeSandboxConfig, } from "../../../sandbox/tenant-config.js";
8
10
  import { fail } from "../../../tenant/http.js";
9
11
  import { clearObservers } from "../../../tenant/session-streams.js";
10
- import { usesFixtureModel } from "../../../tenant/model-setting.js";
12
+ import { readAddedInputs, usesFixtureModel, writeAddedInputs } from "../../../tenant/model-setting.js";
11
13
  import { resetTenant } from "../../../tenant/reset.js";
12
14
  import { clearWork, drain } from "../../../tenant/scheduler.js";
13
15
  import { buildTenantStatus, harnessStatusOf, seedTenantConfig } from "../../../tenant/status.js";
@@ -15,7 +17,7 @@ import { tenantReset, streamsStatus, } from "../../../tenant/streams.js";
15
17
  import { readJson } from "../body.js";
16
18
  import { pathSegments, tenantRoute } from "../define.js";
17
19
  import { jsonResponse } from "../respond.js";
18
- import { rearmPodTimers } from "../../../tenant/sandboxes.js";
20
+ import { rearmPodTimers } from "../../../tenant/environments.js";
19
21
  /** Every Management API route (`/v1/tenant/*`): a management key, acting as itself (protocol 8). */
20
22
  const MANAGEMENT = { credentials: ["management"], scopes: "never" };
21
23
  const json = (schema, description) => ({
@@ -105,7 +107,7 @@ export function tenantRoutes(api) {
105
107
  path: "/v1/tenant/reset",
106
108
  tags: ["Tenant"],
107
109
  summary: "Reset the Tenant",
108
- description: "Deletes its sessions, its sandboxes, or both, after draining or cancelling the turns in progress. The Tenant stays open.",
110
+ description: "Deletes its sessions, its Environments (with their vaults), or both, after draining or cancelling the turns in progress. The Tenant stays open.",
109
111
  request: { body: body(ResetTenantRequest) },
110
112
  responses: { 200: json(ResetTenantResponse, "Reset") },
111
113
  }, async (c) => {
@@ -114,7 +116,7 @@ export function tenantRoutes(api) {
114
116
  await drain(ctx, request.activeWork, 30_000);
115
117
  // A sessions reset moves the Tenant to a new basin generation, so a session created
116
118
  // again with the same id, during or after the reset, starts in an empty basin at 0.
117
- const sessionsReset = request.scope !== "sandboxes";
119
+ const sessionsReset = request.scope !== "environments";
118
120
  await resetTenant({
119
121
  store: ctx.store,
120
122
  sandbox: ctx.sandbox,
@@ -149,29 +151,56 @@ export function tenantRoutes(api) {
149
151
  path: "/v1/tenant/models",
150
152
  tags: ["Tenant"],
151
153
  summary: "List model providers and models",
152
- responses: { 200: json(HostModelCatalog, "Provider and model names") },
154
+ description: "Each model says what it reads (`inputs`: its catalog's inputs and those the operator added) and what may be added (`addableInputs`).",
155
+ responses: { 200: json(HostModelCatalog, "Provider and model names, and their inputs") },
153
156
  }, async (c) => {
154
- c.env.tenant;
155
- return jsonResponse(200, hostModelCatalog());
157
+ const added = await c.env.tenant.store.tx((t) => readAddedInputs(t));
158
+ return jsonResponse(200, hostModelCatalog(added));
159
+ });
160
+ tenantRoute(api, MANAGEMENT, {
161
+ method: "put",
162
+ path: "/v1/tenant/models/inputs",
163
+ tags: ["Tenant"],
164
+ summary: "Set what a catalog model reads",
165
+ description: "Replaces the inputs the operator added to one catalog model (Tenant setting `model.inputs`): only those in its `addableInputs`, which its provider API carries. Its catalog's own inputs always stay; an empty list removes the additions. A message naming a file the Tenant's model does not read is refused with `422 attachment_unsupported`. A custom endpoint sets `settings.inputs` on `PUT /v1/tenant/model` instead.",
166
+ request: { body: body(PutModelInputsRequest) },
167
+ responses: { 200: json(ModelInputsView, "What the model reads now") },
168
+ }, async (c) => {
169
+ const { provider, model, inputs } = PutModelInputsRequestSchema.parse(await readJson(c.req.raw));
170
+ if (provider === "custom")
171
+ fail(400, "A custom endpoint's inputs are its settings: set settings.inputs on PUT /v1/tenant/model.");
172
+ const found = catalogModel(provider, model);
173
+ if (!found)
174
+ fail(400, `Unknown model ${provider}/${model}.`);
175
+ const refused = inputsError(found, inputs);
176
+ if (refused)
177
+ fail(400, refused);
178
+ const added = inputs.filter((input) => addableInputs(found).includes(input));
179
+ await c.env.tenant.store.tx((t) => writeAddedInputs(t, `${provider}/${model}`, added));
180
+ return jsonResponse(200, {
181
+ provider,
182
+ model,
183
+ inputs: capabilitiesOf(found, added).inputs,
184
+ });
156
185
  });
157
186
  tenantRoute(api, MANAGEMENT, {
158
187
  method: "get",
159
188
  path: "/v1/tenant/sandbox",
160
189
  tags: ["Tenant"],
161
- summary: "Get the Tenant's sandbox configuration",
190
+ summary: "Get the Tenant's sandbox backend and Environment settings",
162
191
  responses: { 200: json(TenantSandboxView, "The backend in use and the configuration in force") },
163
192
  }, async (c) => jsonResponse(200, await sandboxView(c.env.tenant)));
164
193
  tenantRoute(api, MANAGEMENT, {
165
194
  method: "put",
166
195
  path: "/v1/tenant/sandbox",
167
196
  tags: ["Tenant"],
168
- summary: "Set the Tenant's sandbox configuration",
169
- description: "What a session gets when it names no sandbox, and the limits every session's sandbox must fit.",
170
- request: { body: body(PutTenantSandboxRequest) },
197
+ summary: "Set the Tenant's Environment settings",
198
+ description: "The limits every Environment's recipe must fit, the pods' stop grace and where harnesses may run (Tenant setting `environments.config`).",
199
+ request: { body: body(PutTenantEnvironmentsRequest) },
171
200
  responses: { 200: json(TenantSandboxView, "The configuration now in force") },
172
201
  }, async (c) => {
173
202
  const ctx = c.env.tenant;
174
- const { requestId: _requestId, ...config } = PutTenantSandboxRequestSchema.parse(await readJson(c.req.raw));
203
+ const { requestId: _requestId, ...config } = PutTenantEnvironmentsRequestSchema.parse(await readJson(c.req.raw));
175
204
  const errors = sandboxConfigErrors(effectiveSandboxConfig(config));
176
205
  if (errors.length > 0)
177
206
  fail(400, errors.join(" "));
@@ -236,6 +265,27 @@ export function tenantRoutes(api) {
236
265
  });
237
266
  return jsonResponse(200, { budgets: rows.map(budgetView) });
238
267
  });
268
+ tenantRoute(api, MANAGEMENT, {
269
+ method: "get",
270
+ path: "/v1/tenant/tool-policy",
271
+ tags: ["Tenant"],
272
+ summary: "Get the Tenant's tool policy",
273
+ responses: { 200: json(ToolPolicy, "The rules the Tool Gate applies to every MCP and HTTP tool call") },
274
+ }, async (c) => jsonResponse(200, await c.env.tenant.store.tx((t) => readToolPolicy(t))));
275
+ tenantRoute(api, MANAGEMENT, {
276
+ method: "put",
277
+ path: "/v1/tenant/tool-policy",
278
+ tags: ["Tenant"],
279
+ summary: "Set the Tenant's tool policy",
280
+ description: "Replaces every rule. The Tool Gate applies them to each MCP and HTTP tool call of every harness, before it sends the call: the strictest rule that matches decides. `deny` refuses the call (the model gets `tool.denied`), `ask` holds it and pauses the turn until a person answers it with an `approve` command on the session, `allow` runs it. A call no rule matches runs. A rule matches when every selector it sets matches: `tool` (the name the model calls it by; `*` matches any run of characters), `server` (an MCP server's name), `kind` (`mcp` or `http`), `agent` (the agent that calls it) and `when` (fields of the call's input by dotted path, each tested with `equals`, `in`, `gt`, `gte`, `lt`, `lte` or `matches`). Sandbox tools never cross the gate. An empty list allows every call.",
281
+ request: { body: body(PutToolPolicyRequest) },
282
+ responses: { 200: json(ToolPolicy, "The policy now in force") },
283
+ }, async (c) => {
284
+ const { rules } = PutToolPolicyRequestSchema.parse(await readJson(c.req.raw));
285
+ const policy = { rules, updatedAt: new Date().toISOString() };
286
+ await c.env.tenant.store.tx((t) => t.putSetting(TOOL_POLICY_SETTING, JSON.stringify(policy)));
287
+ return jsonResponse(200, policy);
288
+ });
239
289
  tenantRoute(api, MANAGEMENT, {
240
290
  method: "get",
241
291
  path: "/v1/tenant/providers",
@@ -0,0 +1,65 @@
1
+ /**
2
+ * Files that arrive inside a message (multimodal messages, M7): the AG-UI and A2A endpoints take
3
+ * a file's bytes inline (base64), store them as an artifact of the message's session, and send
4
+ * the message as `parts` through the session's normal command path, so every check a message
5
+ * with file parts meets applies. A file by URL is refused: the Runtime fetches nothing.
6
+ *
7
+ * A retried message (the same idempotency key) reuses the files the first one stored rather than
8
+ * storing them again, so it replays the first turn instead of conflicting with it. A message the
9
+ * Runtime refuses takes the files it stored with it (`discardFiles`).
10
+ */
11
+ import type { MessagePart } from "@nylorun/core/contracts";
12
+ import { type ArtifactAccess } from "../artifacts/service.js";
13
+ import type { TenantContext } from "../tenant/context.js";
14
+ /** The largest file a message may carry inline, decoded. */
15
+ export declare const INLINE_FILE_MAX_BYTES: number;
16
+ /**
17
+ * The request body of an endpoint that takes inline files: one file at the cap, base64-encoded
18
+ * (a third larger), and the rest of the request.
19
+ */
20
+ export declare const INLINE_BODY_MAX_BYTES: number;
21
+ /** A file sent inline: its name, declared media type and base64 bytes. */
22
+ export interface InlineFile {
23
+ readonly name: string;
24
+ readonly mediaType: string;
25
+ /** The bytes, base64 (standard or URL-safe alphabet). */
26
+ readonly data: string;
27
+ }
28
+ /** One part of an inbound message: text, a file sent inline, or an artifact the Runtime holds. */
29
+ export type InboundPart = {
30
+ readonly type: "text";
31
+ readonly text: string;
32
+ } | ({
33
+ readonly type: "inline";
34
+ } & InlineFile) | {
35
+ readonly type: "artifact";
36
+ readonly artifactId: string;
37
+ };
38
+ /** The 400 for a file sent by URL. */
39
+ export declare function refuseUrl(): never;
40
+ /** A name for a file its client did not name: its place in the message and its type's extension. */
41
+ export declare function inlineFileName(mediaType: string, index: number): string;
42
+ /**
43
+ * Stores `file` as a new artifact of `sessionId`, within the inline cap and the Tenant's
44
+ * artifact limits: `400` for bad base64 or a bad media type, `413 limit_exceeded` past a limit.
45
+ */
46
+ export declare function storeInlineFile(ctx: TenantContext, sessionId: string, file: InlineFile, access: ArtifactAccess): Promise<{
47
+ artifactId: string;
48
+ version: number;
49
+ }>;
50
+ /**
51
+ * The parts of the message the session already took under `idempotencyKey`, when it took one
52
+ * with parts: a retry sends the same files again.
53
+ */
54
+ export declare function priorParts(ctx: TenantContext, sessionId: string, idempotencyKey: string): Promise<readonly MessagePart[] | undefined>;
55
+ /**
56
+ * A message's parts, its inline files stored as artifacts of `sessionId` (or, on a retry, the
57
+ * first message's files in their place, by position). `stored` lists the artifacts this call
58
+ * created, for `discardFiles` when the message is refused.
59
+ */
60
+ export declare function messageParts(ctx: TenantContext, sessionId: string, idempotencyKey: string, inbound: readonly InboundPart[], access: ArtifactAccess): Promise<{
61
+ parts: MessagePart[];
62
+ stored: string[];
63
+ }>;
64
+ /** Deletes the artifacts a refused message stored; one already gone is no error. */
65
+ export declare function discardFiles(ctx: TenantContext, artifactIds: readonly string[], access: ArtifactAccess): Promise<void>;
@@ -0,0 +1,115 @@
1
+ import { MAX_MESSAGE_PARTS } from "@nylorun/core/contracts";
2
+ import { commandKey } from "../core/flow-host.js";
3
+ import { extensionFor, normalizeContentType } from "../artifacts/media-types.js";
4
+ import { deleteArtifact, uploadArtifact } from "../artifacts/service.js";
5
+ import { fail } from "../tenant/http.js";
6
+ /** The largest file a message may carry inline, decoded. */
7
+ export const INLINE_FILE_MAX_BYTES = 20 * 1024 * 1024;
8
+ /**
9
+ * The request body of an endpoint that takes inline files: one file at the cap, base64-encoded
10
+ * (a third larger), and the rest of the request.
11
+ */
12
+ export const INLINE_BODY_MAX_BYTES = 28 * 1024 * 1024;
13
+ const BASE64 = /^[A-Za-z0-9+/_-]*={0,2}$/;
14
+ /** The 400 for a file sent by URL. */
15
+ export function refuseUrl() {
16
+ return fail(400, "File URLs are not fetched yet; send the bytes inline", {
17
+ code: "invalid_request",
18
+ });
19
+ }
20
+ /** A name for a file its client did not name: its place in the message and its type's extension. */
21
+ export function inlineFileName(mediaType, index) {
22
+ const extension = extensionFor(mediaType);
23
+ return `file-${index + 1}${extension ? `.${extension}` : ""}`;
24
+ }
25
+ /**
26
+ * Stores `file` as a new artifact of `sessionId`, within the inline cap and the Tenant's
27
+ * artifact limits: `400` for bad base64 or a bad media type, `413 limit_exceeded` past a limit.
28
+ */
29
+ export async function storeInlineFile(ctx, sessionId, file, access) {
30
+ const data = file.data.replace(/\s+/gu, "");
31
+ if (!BASE64.test(data) || data.length % 4 === 1)
32
+ fail(400, `File "${file.name}" is not valid base64`, { code: "invalid_request" });
33
+ // Three bytes per four characters, before decoding a byte.
34
+ if (Math.floor((data.length * 3) / 4) > INLINE_FILE_MAX_BYTES + 2)
35
+ fail(413, `A file sent inline may hold at most ${INLINE_FILE_MAX_BYTES} bytes`, {
36
+ code: "limit_exceeded",
37
+ details: { limitBytes: INLINE_FILE_MAX_BYTES },
38
+ });
39
+ // Node's base64 decoder reads both alphabets.
40
+ const bytes = Buffer.from(data, "base64");
41
+ if (bytes.byteLength > INLINE_FILE_MAX_BYTES)
42
+ fail(413, `A file sent inline may hold at most ${INLINE_FILE_MAX_BYTES} bytes`, {
43
+ code: "limit_exceeded",
44
+ details: { limitBytes: INLINE_FILE_MAX_BYTES },
45
+ });
46
+ if (bytes.byteLength === 0)
47
+ fail(400, `File "${file.name}" is empty`, { code: "invalid_request" });
48
+ const contentType = normalizeContentType(file.mediaType) ??
49
+ fail(400, `File "${file.name}" has no valid media type`, { code: "invalid_request" });
50
+ const { artifact, version } = await uploadArtifact(ctx, {
51
+ name: file.name,
52
+ contentType,
53
+ sessionId,
54
+ declaredBytes: bytes.byteLength,
55
+ source: "upload",
56
+ }, new Uint8Array(bytes.buffer, bytes.byteOffset, bytes.byteLength), access);
57
+ return { artifactId: artifact.artifactId, version: version.version };
58
+ }
59
+ /**
60
+ * The parts of the message the session already took under `idempotencyKey`, when it took one
61
+ * with parts: a retry sends the same files again.
62
+ */
63
+ export async function priorParts(ctx, sessionId, idempotencyKey) {
64
+ const prior = await ctx.store.tx((t) => t.get("commands", commandKey(sessionId, idempotencyKey)));
65
+ return prior?.command.type === "message" && Array.isArray(prior.command.parts)
66
+ ? prior.command.parts
67
+ : undefined;
68
+ }
69
+ /**
70
+ * A message's parts, its inline files stored as artifacts of `sessionId` (or, on a retry, the
71
+ * first message's files in their place, by position). `stored` lists the artifacts this call
72
+ * created, for `discardFiles` when the message is refused.
73
+ */
74
+ export async function messageParts(ctx, sessionId, idempotencyKey, inbound, access) {
75
+ const parts = inbound.filter((part) => part.type !== "text" || part.text !== "");
76
+ if (parts.length > MAX_MESSAGE_PARTS)
77
+ fail(400, `A message may carry at most ${MAX_MESSAGE_PARTS} parts`, { code: "invalid_request" });
78
+ const prior = parts.some((part) => part.type === "inline")
79
+ ? (await priorParts(ctx, sessionId, idempotencyKey))?.filter((part) => part.type === "file")
80
+ : undefined;
81
+ const out = [];
82
+ const stored = [];
83
+ let files = 0;
84
+ try {
85
+ for (const part of parts) {
86
+ if (part.type === "text") {
87
+ out.push({ type: "text", text: part.text });
88
+ continue;
89
+ }
90
+ const index = files++;
91
+ if (part.type === "artifact") {
92
+ out.push({ type: "file", artifactId: part.artifactId });
93
+ continue;
94
+ }
95
+ const again = prior?.[index];
96
+ if (again) {
97
+ out.push(again);
98
+ continue;
99
+ }
100
+ const file = await storeInlineFile(ctx, sessionId, part, access);
101
+ stored.push(file.artifactId);
102
+ out.push({ type: "file", artifactId: file.artifactId, version: file.version });
103
+ }
104
+ }
105
+ catch (error) {
106
+ await discardFiles(ctx, stored, access);
107
+ throw error;
108
+ }
109
+ return { parts: out, stored };
110
+ }
111
+ /** Deletes the artifacts a refused message stored; one already gone is no error. */
112
+ export async function discardFiles(ctx, artifactIds, access) {
113
+ for (const id of artifactIds)
114
+ await deleteArtifact(ctx, id, access).catch(() => undefined);
115
+ }
@@ -135,6 +135,8 @@ const RUNTIME_TAGS = [
135
135
  "GET /v1/sessions/{sessionId}/manifest",
136
136
  "GET /v1/sessions/{sessionId}/usage",
137
137
  "GET /v1/sessions/{sessionId}/calls/model",
138
+ "GET /v1/sessions/{sessionId}/calls/tool",
139
+ "GET /v1/sessions/{sessionId}/turns/{turnId}/trace",
138
140
  "POST /v1/sessions/{sessionId}/sandbox/{tool}",
139
141
  ],
140
142
  },
@@ -156,17 +158,39 @@ const RUNTIME_TAGS = [
156
158
  operations: ["GET /v1/a2a/agents/{agentId}/card", "POST /v1/a2a/agents/{agentId}"],
157
159
  },
158
160
  {
159
- name: "Sandboxes",
160
- group: "Sandboxes",
161
- description: "Isolated machines an agent works in: create or find one, watch its lifecycle, stop, reset or delete it. In `@nylorun/agents`.",
161
+ name: "Environments",
162
+ group: "Environments",
163
+ description: "Where sessions run: one sandbox (a virtual workspace, a pod with its own volume, or none) made from an environment manifest or inline fields and never changed after that, and one vault of secrets for its shell, MCP servers and HTTP tools. Create or find one, watch its lifecycle, stop, reset or delete it, and write its secrets. In `@nylorun/agents`.",
162
164
  operations: [
163
- "PUT /v1/sandboxes/{sandboxId}",
164
- "GET /v1/sandboxes",
165
- "GET /v1/sandboxes/{sandboxId}",
166
- "GET /v1/sandboxes/{sandboxId}/events",
167
- "POST /v1/sandboxes/{sandboxId}/stop",
168
- "POST /v1/sandboxes/{sandboxId}/reset",
169
- "DELETE /v1/sandboxes/{sandboxId}",
165
+ "PUT /v1/environments/{environmentId}",
166
+ "GET /v1/environments",
167
+ "GET /v1/environments/{environmentId}",
168
+ "GET /v1/environments/{environmentId}/events",
169
+ "POST /v1/environments/{environmentId}/stop",
170
+ "POST /v1/environments/{environmentId}/reset",
171
+ "DELETE /v1/environments/{environmentId}",
172
+ "GET /v1/environments/{environmentId}/secrets",
173
+ "GET /v1/environments/{environmentId}/secrets/{name}",
174
+ "PUT /v1/environments/{environmentId}/secrets/{name}",
175
+ "DELETE /v1/environments/{environmentId}/secrets/{name}",
176
+ "POST /v1/environments/{environmentId}/snapshot",
177
+ "POST /v1/environments/{environmentId}/restore",
178
+ "POST /v1/environments/{environmentId}/fork",
179
+ "GET /v1/environments/{environmentId}/versions",
180
+ "DELETE /v1/environments/{environmentId}/versions/{version}",
181
+ ],
182
+ },
183
+ {
184
+ name: "Environment manifests",
185
+ group: "Environments",
186
+ description: "Named, versioned definitions of a kind of Environment, saved like agents (protocol 14). Save one with a plan first (`?dryRun=true`) and `If-Match`; an Environment is made from one version and keeps it, so a save changes no Environment. In `@nylorun/agents`.",
187
+ operations: [
188
+ "PUT /v1/environment-manifests/{name}",
189
+ "GET /v1/environment-manifests",
190
+ "GET /v1/environment-manifests/{name}",
191
+ "GET /v1/environment-manifests/{name}/versions",
192
+ "GET /v1/environment-manifests/{name}/versions/{version}",
193
+ "DELETE /v1/environment-manifests/{name}",
170
194
  ],
171
195
  },
172
196
  {
@@ -221,9 +245,10 @@ const MANAGEMENT_TAGS = [
221
245
  },
222
246
  {
223
247
  name: "Models",
224
- description: "Model providers, their credentials, the model the Tenant uses, what it spent, every model call for export, and the budgets that cap it. `admin.models`.",
248
+ description: "Model providers, their credentials, the model the Tenant uses, what each model reads, what it spent, every model call for export, and the budgets that cap it. `admin.models`.",
225
249
  operations: [
226
250
  "GET /v1/tenant/models",
251
+ "PUT /v1/tenant/models/inputs",
227
252
  "GET /v1/tenant/providers",
228
253
  "GET /v1/tenant/model",
229
254
  "PUT /v1/tenant/model",
@@ -235,23 +260,18 @@ const MANAGEMENT_TAGS = [
235
260
  ],
236
261
  },
237
262
  {
238
- name: "Vaults",
239
- description: "The installation's and each person's credentials for agents' tools: API keys, bound to the URL they are sent to. Sessions attach a vault by id. `admin.vaults`.",
240
- operations: [
241
- "POST /v1/tenant/vaults",
242
- "GET /v1/tenant/vaults",
243
- "GET /v1/tenant/vaults/{vaultId}",
244
- "DELETE /v1/tenant/vaults/{vaultId}",
245
- "POST /v1/tenant/vaults/{vaultId}/credentials",
246
- "GET /v1/tenant/vaults/{vaultId}/credentials",
247
- "GET /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
248
- "POST /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
249
- "DELETE /v1/tenant/vaults/{vaultId}/credentials/{credentialId}",
250
- ],
263
+ name: "Tool policy",
264
+ description: "The rules the Tool Gate applies to every MCP and HTTP tool call of every harness: refuse it, hold it for a person's approval, or run it. `admin.toolPolicy`.",
265
+ operations: ["GET /v1/tenant/tool-policy", "PUT /v1/tenant/tool-policy"],
266
+ },
267
+ {
268
+ name: "Credential coverage",
269
+ description: "Which secret of an Environment each of an agent's MCP servers and HTTP tools would get, before a session opens. The secrets themselves are written on the Runtime API. `admin.coverage`.",
270
+ operations: ["POST /v1/tenant/credential-coverage"],
251
271
  },
252
272
  {
253
273
  name: "MCP servers",
254
- description: "See a remote MCP server's tools before an agent names it, with the installation vault's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
274
+ description: "See a remote MCP server's tools before an agent names it, with an Environment's credential for its URL: their model names, sizes and annotations, or that it needs a person's sign-in. `admin.mcp`.",
255
275
  operations: ["POST /v1/tenant/mcp/preview"],
256
276
  },
257
277
  {
@@ -265,7 +285,7 @@ const MANAGEMENT_TAGS = [
265
285
  },
266
286
  {
267
287
  name: "Settings",
268
- description: "The Tenant's sandbox and artifact settings. `admin.settings`.",
288
+ description: "The Tenant's sandbox backend, Environment and artifact settings. `admin.settings`.",
269
289
  operations: [
270
290
  "GET /v1/tenant/sandbox",
271
291
  "PUT /v1/tenant/sandbox",
@@ -368,11 +388,11 @@ function documents() {
368
388
  built = {
369
389
  runtime: document(all, RUNTIME_TAGS, {
370
390
  title: "Nylorun Runtime API",
371
- description: "For developers: agents, sessions (with AG-UI and A2A), sandboxes and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps).",
391
+ description: "For developers: agents, sessions (with AG-UI and A2A), Environments and artifacts, through `@nylorun/agents`. Every Tenant serves it on its one URL, beside the Management API (`/openapi/management.json`). Every request sends `Nylorun-Protocol`. Who may call each operation is its `security` and its `x-nylorun-credentials` and `x-nylorun-scopes` (the subject scopes that reach it) fields: an application key (servers) or a trusted issuer's token (browsers and apps).",
372
392
  }),
373
393
  management: document(all, MANAGEMENT_TAGS, {
374
394
  title: "Nylorun Management API",
375
- description: "For operators: the Tenant's models, vaults, signing keys, settings and application keys, at `/v1/tenant/*` on the Tenant's URL, through `@nylorun/admin`. Every operation takes a management key, acting as itself: never for a subject and never from a browser. Management keys are issued only on the Tenant's machine (`nylorun key put <id> --management`) or from `NYLORUN_MANAGEMENT_KEY_FILE`.",
395
+ description: "For operators: the Tenant's models, vaults, signing keys, settings and application keys, at `/v1/tenant/*` on the Tenant's URL, through `@nylorun/admin`. Every operation takes a management key, acting as itself: never for a subject and never from a browser. Management keys are issued only on the Tenant's machine (`nylorun tenant keys put <id> --management`) or from `NYLORUN_MANAGEMENT_KEY_FILE`.",
376
396
  }),
377
397
  };
378
398
  return built;