@nylorun/runtime 0.13.0-beta → 0.15.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 (309) hide show
  1. package/CHANGELOG.md +434 -0
  2. package/README.md +137 -41
  3. package/dist/adapters/execution/restate.d.ts +7 -2
  4. package/dist/adapters/execution/restate.js +40 -10
  5. package/dist/adapters/replication/pgoutput.d.ts +21 -0
  6. package/dist/adapters/replication/pgoutput.js +170 -0
  7. package/dist/adapters/streams/s2.js +37 -20
  8. package/dist/admin-openapi.json +570 -0
  9. package/dist/api/a2a/card.d.ts +15 -0
  10. package/dist/api/a2a/card.js +24 -0
  11. package/dist/api/a2a/endpoint.d.ts +8 -0
  12. package/dist/api/a2a/endpoint.js +81 -0
  13. package/dist/api/a2a/protocol.d.ts +144 -0
  14. package/dist/api/a2a/protocol.js +188 -0
  15. package/dist/api/a2a/routes.d.ts +19 -0
  16. package/dist/api/a2a/routes.js +289 -0
  17. package/dist/api/a2a/tasks.d.ts +50 -0
  18. package/dist/api/a2a/tasks.js +186 -0
  19. package/dist/api/ag-ui/endpoint.d.ts +8 -0
  20. package/dist/api/ag-ui/endpoint.js +126 -0
  21. package/dist/api/ag-ui/history.d.ts +7 -0
  22. package/dist/api/ag-ui/history.js +60 -0
  23. package/dist/api/ag-ui/routes.d.ts +32 -0
  24. package/dist/api/ag-ui/routes.js +305 -0
  25. package/dist/api/ag-ui/session-id.d.ts +1 -0
  26. package/dist/api/ag-ui/session-id.js +12 -0
  27. package/dist/api/ag-ui/sse.d.ts +9 -0
  28. package/dist/api/ag-ui/sse.js +7 -0
  29. package/dist/api/ag-ui/translate.d.ts +38 -0
  30. package/dist/api/ag-ui/translate.js +222 -0
  31. package/dist/api/components.d.ts +8065 -0
  32. package/dist/api/components.js +82 -0
  33. package/dist/api/gate/keys.d.ts +14 -0
  34. package/dist/api/gate/keys.js +59 -0
  35. package/dist/api/gate/routes.d.ts +34 -0
  36. package/dist/api/gate/routes.js +235 -0
  37. package/dist/api/http/app.d.ts +18 -0
  38. package/dist/api/http/app.js +58 -0
  39. package/dist/api/http/body.d.ts +4 -0
  40. package/dist/api/http/body.js +37 -0
  41. package/dist/api/http/define.d.ts +39 -0
  42. package/dist/api/http/define.js +122 -0
  43. package/dist/api/http/respond.d.ts +9 -0
  44. package/dist/api/http/respond.js +44 -0
  45. package/dist/api/http/routes/access.d.ts +24 -0
  46. package/dist/api/http/routes/access.js +266 -0
  47. package/dist/api/http/routes/actions.d.ts +8 -0
  48. package/dist/api/http/routes/actions.js +76 -0
  49. package/dist/api/http/routes/endpoints.d.ts +7 -0
  50. package/dist/api/http/routes/endpoints.js +100 -0
  51. package/dist/api/http/routes/sessions.d.ts +3 -0
  52. package/dist/api/http/routes/sessions.js +205 -0
  53. package/dist/api/http/routes/tenant.d.ts +11 -0
  54. package/dist/api/http/routes/tenant.js +244 -0
  55. package/dist/api/http/routes/vaults.d.ts +8 -0
  56. package/dist/api/http/routes/vaults.js +167 -0
  57. package/dist/api/openapi.d.ts +19 -0
  58. package/dist/api/openapi.js +137 -0
  59. package/dist/api/route.d.ts +12 -0
  60. package/dist/api/route.js +4 -0
  61. package/dist/core/bearer.d.ts +8 -0
  62. package/dist/core/bearer.js +11 -0
  63. package/dist/core/flow-host.d.ts +7 -15
  64. package/dist/core/flow-host.js +16 -41
  65. package/dist/core/sandbox-routes.d.ts +12 -7
  66. package/dist/core/sandbox-routes.js +24 -25
  67. package/dist/execution/memory.d.ts +8 -5
  68. package/dist/execution/memory.js +33 -13
  69. package/dist/execution/types.d.ts +38 -6
  70. package/dist/execution/types.js +12 -3
  71. package/dist/gates/contract.d.ts +51 -0
  72. package/dist/gates/contract.js +37 -0
  73. package/dist/gates/handler.d.ts +21 -0
  74. package/dist/gates/handler.js +55 -0
  75. package/dist/gates/http-client.d.ts +12 -0
  76. package/dist/gates/http-client.js +160 -0
  77. package/dist/gates/in-process.d.ts +32 -0
  78. package/dist/gates/in-process.js +42 -0
  79. package/dist/gates/inflight.d.ts +39 -0
  80. package/dist/gates/inflight.js +70 -0
  81. package/dist/gates/mcp-handler.d.ts +35 -0
  82. package/dist/gates/mcp-handler.js +142 -0
  83. package/dist/gates/meter.d.ts +24 -0
  84. package/dist/gates/meter.js +165 -0
  85. package/dist/gates/model-gate.d.ts +45 -0
  86. package/dist/gates/model-gate.js +1 -0
  87. package/dist/gates/tenant-vaults.d.ts +64 -0
  88. package/dist/gates/tenant-vaults.js +94 -0
  89. package/dist/gates/tool-calls.d.ts +51 -0
  90. package/dist/gates/tool-calls.js +64 -0
  91. package/dist/gates/tool-client.d.ts +12 -0
  92. package/dist/gates/tool-client.js +201 -0
  93. package/dist/gates/tool-contract.d.ts +116 -0
  94. package/dist/gates/tool-contract.js +64 -0
  95. package/dist/gates/tool-gate.d.ts +45 -0
  96. package/dist/gates/tool-gate.js +10 -0
  97. package/dist/host/admin-api.d.ts +23 -0
  98. package/dist/host/admin-api.js +74 -0
  99. package/dist/host/app.d.ts +34 -0
  100. package/dist/host/app.js +181 -0
  101. package/dist/host/config-for.d.ts +3 -1
  102. package/dist/host/config-for.js +3 -2
  103. package/dist/host/config.d.ts +11 -0
  104. package/dist/host/cors.d.ts +27 -0
  105. package/dist/host/cors.js +64 -0
  106. package/dist/host/create-host.d.ts +36 -8
  107. package/dist/host/create-host.js +161 -330
  108. package/dist/host/execution.d.ts +20 -18
  109. package/dist/host/execution.js +2 -2
  110. package/dist/host/gates.d.ts +58 -0
  111. package/dist/host/gates.js +127 -0
  112. package/dist/host/http.d.ts +20 -4
  113. package/dist/host/http.js +81 -23
  114. package/dist/host/index.d.ts +1 -1
  115. package/dist/host/main.js +189 -58
  116. package/dist/host/stack-config.d.ts +91 -19
  117. package/dist/host/stack-config.js +246 -17
  118. package/dist/infra/database.d.ts +2 -2
  119. package/dist/infra/execution.d.ts +5 -5
  120. package/dist/infra/execution.js +8 -6
  121. package/dist/infra/index.d.ts +1 -1
  122. package/dist/infra/workers.d.ts +17 -17
  123. package/dist/infra/workers.js +9 -9
  124. package/dist/keys/client.d.ts +9 -0
  125. package/dist/keys/client.js +70 -0
  126. package/dist/keys/contract.d.ts +24 -0
  127. package/dist/keys/contract.js +12 -0
  128. package/dist/keys/keys.d.ts +41 -0
  129. package/dist/keys/keys.js +50 -0
  130. package/dist/mcp/connect.d.ts +43 -3
  131. package/dist/mcp/connect.js +12 -5
  132. package/dist/mcp/pool.d.ts +39 -3
  133. package/dist/mcp/pool.js +74 -11
  134. package/dist/model/catalog.d.ts +2 -10
  135. package/dist/model/classify.d.ts +13 -0
  136. package/dist/model/classify.js +61 -0
  137. package/dist/model/models.d.ts +10 -1
  138. package/dist/model/models.js +14 -6
  139. package/dist/model/pi-model.d.ts +17 -2
  140. package/dist/model/pi-model.js +366 -222
  141. package/dist/openapi.json +25889 -0
  142. package/dist/record/append.d.ts +51 -0
  143. package/dist/record/append.js +20 -0
  144. package/dist/record/envelope.d.ts +19 -0
  145. package/dist/record/envelope.js +49 -0
  146. package/dist/record/index.d.ts +11 -0
  147. package/dist/record/index.js +11 -0
  148. package/dist/sandbox/manager.d.ts +13 -1
  149. package/dist/sandbox/manager.js +8 -11
  150. package/dist/sandbox/resolve.d.ts +33 -0
  151. package/dist/sandbox/resolve.js +81 -0
  152. package/dist/sandbox/session-sandbox.d.ts +11 -0
  153. package/dist/sandbox/session-sandbox.js +51 -0
  154. package/dist/sandbox/share.d.ts +14 -2
  155. package/dist/sandbox/share.js +33 -3
  156. package/dist/sandbox/tenant-config.d.ts +37 -0
  157. package/dist/sandbox/tenant-config.js +51 -0
  158. package/dist/sandbox/tools.d.ts +7 -9
  159. package/dist/sandbox/tools.js +11 -3
  160. package/dist/sandbox/types.d.ts +8 -2
  161. package/dist/store/postgres/connect.d.ts +25 -9
  162. package/dist/store/postgres/connect.js +27 -10
  163. package/dist/store/postgres/db.d.ts +16 -0
  164. package/dist/store/postgres/db.js +28 -0
  165. package/dist/store/postgres/drizzle/0000_foundation.sql +22 -0
  166. package/dist/store/postgres/drizzle/0001_baseline.sql +255 -0
  167. package/dist/store/postgres/drizzle/0002_stream_relay.sql +4 -0
  168. package/dist/store/postgres/drizzle/0003_tool_crossings.sql +9 -0
  169. package/dist/store/postgres/drizzle/meta/_journal.json +34 -0
  170. package/dist/store/postgres/migrate.d.ts +68 -0
  171. package/dist/store/postgres/migrate.js +183 -0
  172. package/dist/store/postgres/record-writer.d.ts +4 -0
  173. package/dist/store/postgres/record-writer.js +34 -0
  174. package/dist/store/postgres/record.d.ts +5 -0
  175. package/dist/store/postgres/record.js +60 -0
  176. package/dist/store/postgres/schema.d.ts +3096 -0
  177. package/dist/store/postgres/schema.js +363 -0
  178. package/dist/store/postgres/store.d.ts +2 -4
  179. package/dist/store/postgres/store.js +590 -434
  180. package/dist/store/postgres/tenant.d.ts +56 -0
  181. package/dist/store/postgres/tenant.js +154 -0
  182. package/dist/store/types.d.ts +183 -132
  183. package/dist/store/types.js +0 -1
  184. package/dist/streams/basin.d.ts +18 -1
  185. package/dist/streams/basin.js +34 -2
  186. package/dist/streams/control.d.ts +18 -0
  187. package/dist/streams/control.js +33 -0
  188. package/dist/streams/memory.d.ts +3 -1
  189. package/dist/streams/memory.js +14 -8
  190. package/dist/streams/relay/core.d.ts +35 -0
  191. package/dist/streams/relay/core.js +366 -0
  192. package/dist/streams/relay/types.d.ts +78 -0
  193. package/dist/streams/relay/types.js +8 -0
  194. package/dist/streams/types.d.ts +71 -36
  195. package/dist/streams/types.js +18 -53
  196. package/dist/tenant/access-policy.d.ts +30 -0
  197. package/dist/tenant/access-policy.js +52 -0
  198. package/dist/tenant/actions.d.ts +10 -149
  199. package/dist/tenant/actions.js +5 -167
  200. package/dist/tenant/advance.d.ts +7 -2
  201. package/dist/tenant/advance.js +125 -14
  202. package/dist/tenant/auth.d.ts +19 -17
  203. package/dist/tenant/auth.js +90 -93
  204. package/dist/tenant/browser.d.ts +15 -0
  205. package/dist/tenant/browser.js +39 -0
  206. package/dist/tenant/cause.d.ts +25 -0
  207. package/dist/tenant/cause.js +45 -0
  208. package/dist/tenant/commands.d.ts +18 -2
  209. package/dist/tenant/commands.js +111 -115
  210. package/dist/tenant/context.d.ts +101 -21
  211. package/dist/tenant/context.js +14 -10
  212. package/dist/tenant/delivery-token.d.ts +41 -0
  213. package/dist/tenant/delivery-token.js +104 -0
  214. package/dist/tenant/delivery.d.ts +62 -0
  215. package/dist/tenant/delivery.js +481 -0
  216. package/dist/tenant/effects.d.ts +20 -1
  217. package/dist/tenant/effects.js +159 -42
  218. package/dist/tenant/endpoints.d.ts +67 -0
  219. package/dist/tenant/endpoints.js +90 -0
  220. package/dist/tenant/ephemeral.d.ts +22 -7
  221. package/dist/tenant/ephemeral.js +83 -125
  222. package/dist/tenant/history.d.ts +64 -0
  223. package/dist/tenant/history.js +120 -0
  224. package/dist/tenant/http.d.ts +8 -9
  225. package/dist/tenant/http.js +13 -27
  226. package/dist/tenant/jwt.d.ts +16 -0
  227. package/dist/tenant/jwt.js +50 -0
  228. package/dist/tenant/model-setting.d.ts +2 -2
  229. package/dist/tenant/module.d.ts +17 -28
  230. package/dist/tenant/module.js +133 -279
  231. package/dist/tenant/outbound.d.ts +44 -0
  232. package/dist/tenant/outbound.js +127 -0
  233. package/dist/tenant/paths.d.ts +11 -16
  234. package/dist/tenant/paths.js +24 -68
  235. package/dist/tenant/pool.d.ts +1 -3
  236. package/dist/tenant/pool.js +1 -19
  237. package/dist/tenant/principals.d.ts +23 -11
  238. package/dist/tenant/principals.js +61 -49
  239. package/dist/tenant/reset.d.ts +0 -4
  240. package/dist/tenant/reset.js +0 -5
  241. package/dist/tenant/runtime.d.ts +28 -20
  242. package/dist/tenant/runtime.js +95 -64
  243. package/dist/tenant/scheduler.d.ts +10 -3
  244. package/dist/tenant/scheduler.js +9 -5
  245. package/dist/tenant/session-streams.d.ts +127 -0
  246. package/dist/tenant/session-streams.js +404 -0
  247. package/dist/tenant/sessions.d.ts +28 -4
  248. package/dist/tenant/sessions.js +118 -24
  249. package/dist/tenant/signing-keys.d.ts +55 -0
  250. package/dist/tenant/signing-keys.js +172 -0
  251. package/dist/tenant/slim.d.ts +8 -0
  252. package/dist/tenant/slim.js +21 -0
  253. package/dist/tenant/status.d.ts +3 -3
  254. package/dist/tenant/status.js +20 -13
  255. package/dist/tenant/store-pg.d.ts +9 -6
  256. package/dist/tenant/store-pg.js +60 -125
  257. package/dist/tenant/streams.d.ts +62 -85
  258. package/dist/tenant/streams.js +143 -226
  259. package/dist/tenant/subject-limits.d.ts +16 -0
  260. package/dist/tenant/subject-limits.js +45 -0
  261. package/dist/tenant/sweep.d.ts +1 -10
  262. package/dist/tenant/sweep.js +29 -69
  263. package/dist/tenant/tokens.d.ts +14 -0
  264. package/dist/tenant/tokens.js +180 -0
  265. package/dist/tenant/transcript.d.ts +48 -5
  266. package/dist/tenant/transcript.js +58 -1
  267. package/dist/tenant/types.d.ts +69 -98
  268. package/dist/tenant/types.js +4 -15
  269. package/dist/tenant/worker.d.ts +3 -1
  270. package/dist/tenant/worker.js +13 -7
  271. package/dist/vault/host-model.d.ts +31 -0
  272. package/dist/vault/host-model.js +57 -0
  273. package/dist/vault/service.d.ts +44 -17
  274. package/dist/vault/service.js +126 -121
  275. package/dist/version.d.ts +1 -1
  276. package/dist/version.js +1 -1
  277. package/package.json +25 -9
  278. package/dist/core/executors.d.ts +0 -44
  279. package/dist/core/executors.js +0 -95
  280. package/dist/store/memory.d.ts +0 -57
  281. package/dist/store/memory.js +0 -645
  282. package/dist/store/postgres/migrations/001_initial.d.ts +0 -27
  283. package/dist/store/postgres/migrations/001_initial.js +0 -196
  284. package/dist/store/postgres/migrations/002_session_owner.d.ts +0 -6
  285. package/dist/store/postgres/migrations/002_session_owner.js +0 -13
  286. package/dist/store/postgres/migrations/index.d.ts +0 -49
  287. package/dist/store/postgres/migrations/index.js +0 -76
  288. package/dist/store/postgres/names.d.ts +0 -11
  289. package/dist/store/postgres/names.js +0 -42
  290. package/dist/store/postgres/tenants.d.ts +0 -90
  291. package/dist/store/postgres/tenants.js +0 -214
  292. package/dist/streams/relay.d.ts +0 -37
  293. package/dist/streams/relay.js +0 -254
  294. package/dist/tenant/envelope.d.ts +0 -7
  295. package/dist/tenant/envelope.js +0 -22
  296. package/dist/tenant/live.d.ts +0 -92
  297. package/dist/tenant/live.js +0 -290
  298. package/dist/tenant/quarantine-error.d.ts +0 -6
  299. package/dist/tenant/quarantine-error.js +0 -9
  300. package/dist/tenant/quarantine.d.ts +0 -27
  301. package/dist/tenant/quarantine.js +0 -72
  302. package/dist/tenant/routes-tenant.d.ts +0 -12
  303. package/dist/tenant/routes-tenant.js +0 -137
  304. package/dist/tenant/routes.d.ts +0 -9
  305. package/dist/tenant/routes.js +0 -151
  306. package/dist/tenant/store-memory.d.ts +0 -16
  307. package/dist/tenant/store-memory.js +0 -111
  308. /package/dist/{store → record}/cursor.d.ts +0 -0
  309. /package/dist/{store → record}/cursor.js +0 -0
@@ -0,0 +1,181 @@
1
+ import { Hono } from "hono";
2
+ import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
3
+ import { checkCompatibility, HOST_PROTOCOL, PROTOCOL_HEADER, PUBLISHABLE_KEY_HEADER, TENANT_HEADER, tenantOfPublishableKey, } from "@nylorun/core/compatibility";
4
+ import { RUNTIME_VERSION } from "../version.js";
5
+ import { findTenantRoute } from "../api/http/app.js";
6
+ import { tenantDocument } from "../api/openapi.js";
7
+ import { answerPreflight } from "./cors.js";
8
+ import { adminKeyMatches, headerValue, isJsonContentType, jsonResponse, opaqueNotFoundResponse, protocolRejectedResponse, readBearer, pathnameIsLogged, redactRoutePath, rejectedResponse, requestHasBody, } from "./http.js";
9
+ export function createHostApp(options) {
10
+ const { module, logger } = options;
11
+ const app = new Hono();
12
+ app.use(async (c, next) => {
13
+ const started = Date.now();
14
+ try {
15
+ await next();
16
+ }
17
+ finally {
18
+ const { incoming } = c.env;
19
+ if (pathnameIsLogged(incoming.url))
20
+ logger.info("request", {
21
+ status: statusOf(c),
22
+ tenantId: c.get("tenantId"),
23
+ durationMs: Date.now() - started,
24
+ path: redactRoutePath(pathnameOf(incoming)),
25
+ method: incoming.method,
26
+ });
27
+ }
28
+ });
29
+ app.use(async (c, next) => {
30
+ const { incoming, outgoing, role } = c.env;
31
+ if (headerValue(incoming, "origin") !== undefined) {
32
+ const route = pathnameOf(incoming).split("/").filter(Boolean);
33
+ // Only Tenant routes, and only when the operator allows browsers; the Tenant then
34
+ // checks the publishable key and its origins before adding any CORS header.
35
+ const tenantRoute = route[0] === "v1" && route[1] !== "admin";
36
+ // The operator listener never serves browsers.
37
+ if (!options.browserAccess || role === "operator" || !tenantRoute)
38
+ return rejectedResponse(403, "origin_rejected", "Browser Origin headers are not accepted");
39
+ if (incoming.method === "OPTIONS") {
40
+ c.set("status", answerPreflight(incoming, outgoing, route, browserRoute));
41
+ return RESPONSE_ALREADY_SENT;
42
+ }
43
+ }
44
+ await next();
45
+ });
46
+ app.use(async (c, next) => {
47
+ const { incoming } = c.env;
48
+ if (requestHasBody(incoming) && !isJsonContentType(headerValue(incoming, "content-type")))
49
+ return rejectedResponse(415, "unsupported_media_type", "Request bodies must use application/json");
50
+ await next();
51
+ });
52
+ app.all("*", async (c) => {
53
+ const { incoming, outgoing, role } = c.env;
54
+ const url = new URL(incoming.url ?? "/", "http://runtime.local");
55
+ const pathname = url.pathname;
56
+ if (pathname === "/health")
57
+ return jsonResponse(200, {
58
+ status: "ok",
59
+ service: "nylorun-runtime",
60
+ version: RUNTIME_VERSION,
61
+ protocol: {
62
+ min: HOST_PROTOCOL.min,
63
+ max: HOST_PROTOCOL.max,
64
+ features: [...HOST_PROTOCOL.features],
65
+ },
66
+ coreVersion: options.coreVersion,
67
+ hostId: options.hostId,
68
+ pid: options.pid,
69
+ });
70
+ if (pathname === "/ready") {
71
+ const listener = options.listening();
72
+ const tenant = module.ready;
73
+ const infra = await options.readiness?.();
74
+ const ready = listener && tenant && !options.closing() && (infra?.ok ?? true);
75
+ return jsonResponse(ready ? 200 : 503, {
76
+ status: ready ? "ready" : "not_ready",
77
+ service: "nylorun-runtime",
78
+ checks: { listener, tenant, ...infra?.checks },
79
+ });
80
+ }
81
+ // This API's own description: public, as the npm package that ships it.
82
+ if (pathname === "/openapi.json" && incoming.method === "GET")
83
+ return jsonResponse(200, tenantDocument(), { "cache-control": "no-cache" });
84
+ const segments = pathname.split("/").filter(Boolean);
85
+ if (segments[0] === "v1" && segments[1] === "admin") {
86
+ // A public listener has no admin routes: the same 404 as a wrong admin key.
87
+ if (role === "public")
88
+ return opaqueNotFoundResponse();
89
+ if (!protocolAccepted(headerValue(incoming, PROTOCOL_HEADER)))
90
+ return protocolRejectedResponse();
91
+ const token = readBearer(headerValue(incoming, "authorization"));
92
+ if (!adminKeyMatches(token, options.adminKey))
93
+ return opaqueNotFoundResponse();
94
+ return await options.admin(c.req.raw, { incoming, outgoing });
95
+ }
96
+ // Tenant routes: key pattern → protocol → the Tenant → selection → the Tenant's routes.
97
+ const keyHeader = headerValue(incoming, PUBLISHABLE_KEY_HEADER);
98
+ const keyTenant = keyHeader === undefined ? undefined : tenantOfPublishableKey(keyHeader);
99
+ if (keyHeader !== undefined && keyTenant === undefined)
100
+ return rejectedResponse(400, "invalid_request", `${PUBLISHABLE_KEY_HEADER} header is malformed`);
101
+ const tenantHeader = headerValue(incoming, TENANT_HEADER);
102
+ const named = tenantHeader === undefined || tenantHeader.trim() === "" ? undefined : tenantHeader.trim();
103
+ if (!protocolAccepted(headerValue(incoming, PROTOCOL_HEADER)))
104
+ return protocolRejectedResponse();
105
+ const resolution = await module.resolve();
106
+ if (resolution.kind !== "open") {
107
+ if (resolution.cause)
108
+ logger.warn("tenant_unavailable", {
109
+ code: resolution.cause.code,
110
+ repair: resolution.cause.repair,
111
+ });
112
+ return opaqueNotFoundResponse();
113
+ }
114
+ const { handle } = resolution;
115
+ const tenantId = handle.envelope.id;
116
+ // A protocol 4 client names the Tenant; a publishable key carries one. Either naming
117
+ // another Tenant (or a malformed header) reached the wrong installation.
118
+ if ((named !== undefined && named !== tenantId) || (keyTenant !== undefined && keyTenant !== tenantId))
119
+ return opaqueNotFoundResponse();
120
+ c.set("tenantId", tenantId);
121
+ return await handle.fetch(c.req.raw, { incoming, outgoing });
122
+ });
123
+ app.onError((error, c) => {
124
+ const status = error.status;
125
+ let rejection;
126
+ if (typeof status === "number" && status >= 400 && status < 600) {
127
+ rejection = {
128
+ status,
129
+ code: status === 400 ? "invalid_request" : "request_rejected",
130
+ message: error.message,
131
+ };
132
+ }
133
+ else if (error.name === "ZodError") {
134
+ rejection = { status: 400, code: "invalid_request", message: "Invalid request body" };
135
+ }
136
+ else {
137
+ logger.error("request_failed", { error: error.message });
138
+ rejection = { status: 500, code: "internal_error", message: "Internal error" };
139
+ }
140
+ c.set("status", rejection.status);
141
+ const { outgoing } = c.env;
142
+ // Once a response has started, the rejection can only end it.
143
+ if (outgoing.headersSent) {
144
+ outgoing.end();
145
+ return RESPONSE_ALREADY_SENT;
146
+ }
147
+ return rejectedResponse(rejection.status, rejection.code, rejection.message);
148
+ });
149
+ return app;
150
+ }
151
+ /** A route a browser page may call, decided from the route alone (`api/http/define.ts`). */
152
+ function browserRoute(method, segments) {
153
+ return findTenantRoute(method, segments)?.browser === true;
154
+ }
155
+ /** The answer's status: a Response's, or the Node response's when it was written there. */
156
+ function statusOf(c) {
157
+ const logged = c.get("status");
158
+ if (logged !== undefined)
159
+ return logged;
160
+ if (c.res.headers.has(ALREADY_SENT))
161
+ return c.env.outgoing.statusCode || 200;
162
+ return c.res.status;
163
+ }
164
+ const ALREADY_SENT = "x-hono-already-sent";
165
+ function pathnameOf(incoming) {
166
+ return new URL(incoming.url ?? "/", "http://runtime.local").pathname;
167
+ }
168
+ function parseProtocolVersion(raw) {
169
+ if (raw === undefined)
170
+ return undefined;
171
+ const trimmed = raw.trim();
172
+ if (!/^\d+$/.test(trimmed))
173
+ return undefined;
174
+ return Number(trimmed);
175
+ }
176
+ function protocolAccepted(raw) {
177
+ const version = parseProtocolVersion(raw);
178
+ if (version === undefined)
179
+ return false;
180
+ return checkCompatibility({ version, required: [] }, HOST_PROTOCOL).ok;
181
+ }
@@ -1,7 +1,7 @@
1
1
  import type { HostConfigFile } from "./config.js";
2
2
  import type { Logger, TenantConfig } from "../tenant/types.js";
3
3
  /**
4
- * Builds a TenantConfig for `openTenantRuntime`.
4
+ * Builds the TenantConfig of the Host's Tenant for `openTenantRuntime`.
5
5
  * The sandbox backend here is the Host default; a Tenant's seeded `sandbox.backend`
6
6
  * setting overrides it when the Tenant opens (I1, A18).
7
7
  */
@@ -14,4 +14,6 @@ export declare function configForFactory(options: {
14
14
  mode?: TenantConfig["mode"];
15
15
  /** Override model; default vault. Fixture/scripted require ephemeral/test mode. */
16
16
  model?: TenantConfig["model"];
17
+ /** How Tenants may call Action endpoints (`StackConfig.delivery`). */
18
+ delivery?: TenantConfig["delivery"];
17
19
  }): (id: string) => TenantConfig;
@@ -1,14 +1,14 @@
1
1
  import { tenantPaths } from "../tenant/paths.js";
2
2
  import { tenantChildEnvironment } from "./environment.js";
3
3
  /**
4
- * Builds a TenantConfig for `openTenantRuntime`.
4
+ * Builds the TenantConfig of the Host's Tenant for `openTenantRuntime`.
5
5
  * The sandbox backend here is the Host default; a Tenant's seeded `sandbox.backend`
6
6
  * setting overrides it when the Tenant opens (I1, A18).
7
7
  */
8
8
  export function configForFactory(options) {
9
9
  const { baseline } = options;
10
10
  return (id) => {
11
- const tenant = tenantPaths(options.hostRoot, id);
11
+ const tenant = tenantPaths(options.hostRoot);
12
12
  const sandboxBackend = "auto";
13
13
  return {
14
14
  tenantId: id,
@@ -16,6 +16,7 @@ export function configForFactory(options) {
16
16
  paths: tenant,
17
17
  sandbox: { backend: sandboxBackend },
18
18
  model: options.model ?? { kind: "vault" },
19
+ ...(options.delivery ? { delivery: options.delivery } : {}),
19
20
  childEnv: tenantChildEnvironment(baseline, options.hostConfig, tenant),
20
21
  logger: {
21
22
  info: (message, fields) => options.logger.info(message, { tenantId: id, ...fields }),
@@ -4,6 +4,17 @@ export interface HostConfigFile {
4
4
  host: string;
5
5
  port: number;
6
6
  allowNonLoopback?: boolean;
7
+ /**
8
+ * Whether browser requests (an `Origin` with a publishable key) may reach Tenant routes.
9
+ * Default off for a Host started from host.json; container mode reads
10
+ * `NYLORUN_BROWSER_ACCESS` instead.
11
+ */
12
+ browserAccess?: boolean;
13
+ /**
14
+ * The operator listener's port: the Admin API moves there (on `host`, loopback only), and
15
+ * `port` serves the Tenant API alone. Absent: one port serves both.
16
+ */
17
+ adminPort?: number;
7
18
  proxy?: {
8
19
  httpsProxy?: string;
9
20
  noProxy?: string;
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Browser access at the Host (Host feature `browser-access`). A preflight carries no
3
+ * credentials and no header values, so it cannot name a Tenant: the Host answers it from the
4
+ * route alone, permissively for browser routes, and the Tenant enforces the publishable key's
5
+ * origin allowlist on the actual request (`tenant/browser.ts`). A cached preflight therefore
6
+ * never widens access, and it grants no credentials.
7
+ */
8
+ import type { IncomingMessage, ServerResponse } from "node:http";
9
+ /** Request headers a browser may send to the Runtime. */
10
+ export declare const BROWSER_REQUEST_HEADERS: readonly ["authorization", "content-type", "nylorun-key", "nylorun-tenant", "nylorun-protocol", "last-event-id"];
11
+ /**
12
+ * Tenant routes a browser may call: agents (list), sessions (not the sandbox tool route),
13
+ * vaults, AG-UI and the JWKS. Tokens, access management, endpoints, actions and Tenant
14
+ * settings never are.
15
+ */
16
+ /** The `Origin` header if it is a serialized origin (not `null`, not a path). */
17
+ export declare function browserOrigin(request: IncomingMessage): string | undefined;
18
+ /**
19
+ * Answers a CORS preflight: `204` with the allowed methods and headers for a browser route,
20
+ * `403` without any CORS header otherwise.
21
+ */
22
+ export declare function answerPreflight(request: IncomingMessage, response: ServerResponse, segments: readonly string[], isBrowserRoute: (method: string, segments: readonly string[]) => boolean): number;
23
+ /**
24
+ * CORS headers for an actual request from an allowed origin; set once with `setHeader`, so
25
+ * every response that follows (JSON, errors, SSE) carries them.
26
+ */
27
+ export declare function setCorsHeaders(response: ServerResponse, origin: string): void;
@@ -0,0 +1,64 @@
1
+ import { isSerializedOrigin } from "@nylorun/core/contracts";
2
+ /** Request headers a browser may send to the Runtime. */
3
+ export const BROWSER_REQUEST_HEADERS = [
4
+ "authorization",
5
+ "content-type",
6
+ "nylorun-key",
7
+ "nylorun-tenant",
8
+ "nylorun-protocol",
9
+ "last-event-id",
10
+ ];
11
+ const BROWSER_METHODS = ["GET", "POST", "PUT", "DELETE"];
12
+ const PREFLIGHT_MAX_AGE_SECONDS = 600;
13
+ /**
14
+ * Tenant routes a browser may call: agents (list), sessions (not the sandbox tool route),
15
+ * vaults, AG-UI and the JWKS. Tokens, access management, endpoints, actions and Tenant
16
+ * settings never are.
17
+ */
18
+ /** The `Origin` header if it is a serialized origin (not `null`, not a path). */
19
+ export function browserOrigin(request) {
20
+ const raw = request.headers.origin;
21
+ const origin = Array.isArray(raw) ? raw[0] : raw;
22
+ return origin !== undefined && isSerializedOrigin(origin) ? origin : undefined;
23
+ }
24
+ /**
25
+ * Answers a CORS preflight: `204` with the allowed methods and headers for a browser route,
26
+ * `403` without any CORS header otherwise.
27
+ */
28
+ export function answerPreflight(request, response, segments, isBrowserRoute) {
29
+ const origin = browserOrigin(request);
30
+ const method = String(request.headers["access-control-request-method"] ?? "").toUpperCase();
31
+ const requested = String(request.headers["access-control-request-headers"] ?? "")
32
+ .split(",")
33
+ .map((name) => name.trim().toLowerCase())
34
+ .filter(Boolean);
35
+ const allowed = origin !== undefined &&
36
+ BROWSER_METHODS.includes(method) &&
37
+ isBrowserRoute(method, segments) &&
38
+ requested.every((name) => BROWSER_REQUEST_HEADERS.includes(name));
39
+ if (!allowed) {
40
+ response.writeHead(403, { "content-length": "0" });
41
+ response.end();
42
+ return 403;
43
+ }
44
+ response.writeHead(204, {
45
+ "access-control-allow-origin": origin,
46
+ "access-control-allow-methods": BROWSER_METHODS.join(", "),
47
+ "access-control-allow-headers": requested.join(", "),
48
+ "access-control-max-age": String(PREFLIGHT_MAX_AGE_SECONDS),
49
+ vary: "Origin, Access-Control-Request-Method, Access-Control-Request-Headers",
50
+ "content-length": "0",
51
+ });
52
+ response.end();
53
+ return 204;
54
+ }
55
+ /**
56
+ * CORS headers for an actual request from an allowed origin; set once with `setHeader`, so
57
+ * every response that follows (JSON, errors, SSE) carries them.
58
+ */
59
+ export function setCorsHeaders(response, origin) {
60
+ response.setHeader("access-control-allow-origin", origin);
61
+ response.setHeader("vary", "Origin");
62
+ response.setHeader("access-control-expose-headers", "retry-after, www-authenticate");
63
+ response.setHeader("cache-control", "no-store");
64
+ }
@@ -1,4 +1,4 @@
1
- import { type Logger, type TenantModule } from "../tenant/types.js";
1
+ import type { Logger, TenantModule } from "../tenant/types.js";
2
2
  import type { HostConfigFile, HostCredentialsFile } from "./config.js";
3
3
  import type { ContainerListen } from "./stack-config.js";
4
4
  export interface CreateHostOptions {
@@ -23,20 +23,34 @@ export interface CreateHostOptions {
23
23
  * mode is `http://0.0.0.0:4000`.
24
24
  */
25
25
  publicUrl?: string;
26
+ /**
27
+ * Browser access (Host feature `browser-access`): requests with an `Origin` may reach Tenant
28
+ * routes, where the publishable key's origin allowlist decides. Off by default: every
29
+ * `Origin` is `403 origin_rejected`, as before. Admin routes, `/health` and `/ready` refuse
30
+ * `Origin` either way.
31
+ */
32
+ browserAccess?: boolean;
33
+ /**
34
+ * The operator listener: the Admin API (and the Tenant API, never with browser access) on
35
+ * its own address, kept off the network that reaches the Tenant API. When set, the main
36
+ * listener is public and answers admin routes with the opaque 404. Absent: one listener
37
+ * serves everything, as before.
38
+ */
39
+ operator?: OperatorListen;
26
40
  /**
27
41
  * Infrastructure readiness (`infra/readiness.ts`). `/ready` adds its checks
28
- * and answers 503 while it reports not ok. Default: listener and discovery
29
- * only.
42
+ * and answers 503 while it reports not ok. Default: the listener and the
43
+ * Tenant only.
30
44
  */
31
45
  readiness?: () => Promise<{
32
46
  ok: boolean;
33
47
  checks: Record<string, boolean>;
34
48
  }>;
35
49
  /**
36
- * Shutdown steps around closing the Tenants. `close()` runs them whatever asked for it
50
+ * Shutdown steps around closing the Tenant. `close()` runs them whatever asked for it
37
51
  * (SIGTERM in `host/main.ts`, `POST /v1/admin/host/shutdown`): the listener stops, then
38
- * `beforeTenants` (stop the Worker so no advance starts on a closing Tenant), the Tenants
39
- * close, then `afterTenants` (end the infrastructure clients). A failing step is logged
52
+ * `beforeTenants` (stop the Worker so no advance starts on the closing Tenant), the Tenant
53
+ * closes, then `afterTenants` (end the infrastructure clients). A failing step is logged
40
54
  * and shutdown goes on.
41
55
  */
42
56
  shutdown?: {
@@ -44,13 +58,27 @@ export interface CreateHostOptions {
44
58
  afterTenants?(): Promise<void>;
45
59
  };
46
60
  }
61
+ /** Where the operator listener binds, and the `Host` values it accepts. */
62
+ export interface OperatorListen {
63
+ host: string;
64
+ port: number;
65
+ /** Exact `Host` values (lowercase `name:port`); absent means the loopback forms of the port. */
66
+ allowedHosts?: readonly string[];
67
+ }
68
+ /**
69
+ * What a listener serves. `combined`: everything (one port). `public`: the Tenant API, with
70
+ * browser access when enabled; admin routes are the opaque 404. `operator`: the Admin API and
71
+ * the Tenant API, never to browsers.
72
+ */
73
+ export type ListenerRole = "combined" | "public" | "operator";
47
74
  export interface HostServer {
48
75
  listen(): Promise<void>;
49
76
  close(): Promise<void>;
50
77
  /** Settles once `close()` has finished, whatever called it. */
51
78
  readonly closed: Promise<void>;
52
79
  readonly url: string;
80
+ /** Where the Admin API answers: the operator listener, or `url` when there is one listener. */
81
+ readonly adminUrl: string;
53
82
  }
54
- /** Constant-time comparison of SHA-256 digests of the presented and stored admin keys. */
55
- export declare function adminKeyMatches(presented: string | undefined, adminKey: string): boolean;
83
+ export { adminKeyMatches } from "./http.js";
56
84
  export declare function createHost(options: CreateHostOptions): HostServer;