@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
@@ -34,13 +34,12 @@
34
34
  * listHostProviders(): Promise<{ providers: HostModelProviderInfo[] }>
35
35
  * putHostModel(body: PutHostModelRequest): Promise<HostModelView>
36
36
  * selectHostModel(body: SelectHostModelRequest): Promise<HostModelView>
37
- * readHostModel(): Promise<HostModelSecret | undefined>
38
- * updateHostCredential(credential): Promise<void>
39
- *
40
- * // Rejected caller audit: opens its own transaction.
41
- * reject(route: string): Promise<void>
42
37
  * ```
43
38
  *
39
+ * Reading the host model's secret and writing back a refreshed one are `HostModelVault`'s
40
+ * (`vault/host-model.ts`), which only the Model Gate uses: this service seals the host model's
41
+ * credential but never reads it back.
42
+ *
44
43
  * ## Transactions and I/O
45
44
  *
46
45
  * - No network I/O runs inside a transaction. The KEK getter may touch the
@@ -67,16 +66,21 @@ import { VaultError } from "./error.js";
67
66
  import { hostModelCatalog } from "../model/catalog.js";
68
67
  import { normalizeVaultUrl } from "./url.js";
69
68
  const REFRESH_SKEW_MS = 60_000;
70
- const HOST_VAULT_ID = "host";
69
+ const REFRESH_TIMEOUT_MS = 30_000;
70
+ export const HOST_VAULT_ID = "host";
71
71
  const HOST_MODEL_ID = "host-model";
72
72
  export class VaultService {
73
73
  store;
74
74
  kek;
75
75
  fetchImpl;
76
+ refreshTimeoutMs;
77
+ /** Refreshes in progress by credential id: concurrent uses share one. */
78
+ refreshing = new Map();
76
79
  constructor(options) {
77
80
  this.store = options.store;
78
81
  this.kek = options.kek;
79
82
  this.fetchImpl = options.fetch;
83
+ this.refreshTimeoutMs = options.refreshTimeoutMs ?? REFRESH_TIMEOUT_MS;
80
84
  }
81
85
  // --- administration --------------------------------------------------------
82
86
  async createVault(body) {
@@ -338,7 +342,7 @@ export class VaultService {
338
342
  };
339
343
  }
340
344
  if (row.type === "oauth" && dueForRefresh(row.expiresAt)) {
341
- const refreshed = await this.refresh(kek, row, payload, input.sessionId, url);
345
+ const refreshed = await this.refreshShared(kek, row, payload, input.sessionId, url);
342
346
  if (refreshed.status === "refused")
343
347
  return refreshed;
344
348
  payload = refreshed.payload;
@@ -373,9 +377,9 @@ export class VaultService {
373
377
  }
374
378
  async listHostProviders() {
375
379
  return this.store.tx(async (t) => {
376
- const active = await this.activeProviderId(t);
380
+ const active = await activeProviderId(t);
377
381
  const catalog = new Map(hostModelCatalog().providers.map((provider) => [provider.id, provider.name]));
378
- const providers = (await this.hostModelRows(t)).map((row) => {
382
+ const providers = (await hostModelRows(t)).map((row) => {
379
383
  const binding = modelBinding(row);
380
384
  return {
381
385
  id: binding.provider,
@@ -386,6 +390,7 @@ export class VaultService {
386
390
  model: binding.model,
387
391
  authType: binding.authType,
388
392
  ...(binding.baseUrl ? { baseUrl: binding.baseUrl } : {}),
393
+ ...(binding.settings ? { settings: binding.settings } : {}),
389
394
  lastUpdated: row.rotatedAt ?? row.createdAt,
390
395
  active: binding.provider === active,
391
396
  };
@@ -412,6 +417,7 @@ export class VaultService {
412
417
  provider: body.provider,
413
418
  model: body.model,
414
419
  baseUrl: body.baseUrl,
420
+ settings: body.settings,
415
421
  authType: body.auth.type,
416
422
  payload,
417
423
  });
@@ -430,7 +436,7 @@ export class VaultService {
430
436
  const kek = this.kek();
431
437
  return this.store.tx((t) => this.replay(t, `host-model-select:${body.idempotencyKey}`, body, async () => {
432
438
  this.validateHostModel(body);
433
- const row = await this.hostCredentialRowFor(t, body.provider);
439
+ const row = await hostCredentialRowFor(t, body.provider);
434
440
  if (!row)
435
441
  throw new VaultError(404, "Model provider is not configured");
436
442
  const binding = modelBinding(row);
@@ -442,6 +448,8 @@ export class VaultService {
442
448
  provider: body.provider,
443
449
  model: body.model,
444
450
  baseUrl: body.baseUrl,
451
+ // Selecting a model keeps the endpoint's settings.
452
+ settings: binding.settings,
445
453
  authType: binding.authType,
446
454
  payload,
447
455
  });
@@ -456,61 +464,47 @@ export class VaultService {
456
464
  return this.hostModelView(t);
457
465
  }));
458
466
  }
459
- /** Reads the active host model secret. Decrypts after the read commits. */
460
- async readHostModel() {
461
- const row = await this.store.tx((t) => this.activeHostCredentialRow(t));
462
- if (!row)
463
- return undefined;
464
- const binding = modelBinding(row);
465
- const payload = readHostPayload(this.kek(), row, binding);
466
- const credential = binding.authType === "oauth"
467
- ? { type: "oauth", ...payload.oauth }
468
- : {
469
- type: "api_key",
470
- key: payload.apiKey,
471
- ...(payload.env ? { env: payload.env } : {}),
472
- };
473
- return {
474
- provider: binding.provider,
475
- model: binding.model,
476
- ...(binding.baseUrl ? { baseUrl: binding.baseUrl } : {}),
477
- authType: binding.authType,
478
- credential,
479
- };
480
- }
481
- async updateHostCredential(credential) {
482
- const kek = this.kek();
483
- await this.store.tx(async (t) => {
484
- const row = await this.activeHostCredentialRow(t);
485
- if (!row)
486
- throw new VaultError(404, "Model provider is not configured");
487
- const binding = modelBinding(row);
488
- const payload = credential.type === "oauth"
489
- ? { oauth: credential }
490
- : {
491
- apiKey: credential.key,
492
- ...(credential.env ? { env: credential.env } : {}),
493
- };
494
- const sealed = encryptSecret(kek, hostModelAad(binding.provider, binding.model), Buffer.from(JSON.stringify(payload), "utf8"));
495
- await t.updateCredential(HOST_VAULT_ID, row.id, {
496
- type: "model",
497
- bindingJson: JSON.stringify({ ...binding, authType: credential.type }),
498
- ...sealed,
499
- rotatedAt: new Date().toISOString(),
500
- });
501
- });
502
- }
503
- // --- rejected callers ---------------------------------------------------------
504
- async reject(route) {
505
- await this.store.tx((t) => this.audit(t, {
506
- actor: "executor",
507
- action: "reject",
508
- target: route,
509
- outcome: "rejected",
510
- }));
511
- }
512
467
  // --- internals -----------------------------------------------------------------
513
468
  /** Calls the token endpoint outside any transaction, then stores the result. */
469
+ /**
470
+ * Refreshes an OAuth grant once for every concurrent use in this process. A rotating refresh
471
+ * token is valid once, so two refreshes with it would fail the second, or make the provider
472
+ * revoke the grant.
473
+ */
474
+ refreshShared(kek, row, payload, sessionId, url) {
475
+ let pending = this.refreshing.get(row.id);
476
+ if (!pending) {
477
+ pending = this.refreshLatest(kek, row, payload, sessionId, url).finally(() => this.refreshing.delete(row.id));
478
+ this.refreshing.set(row.id, pending);
479
+ }
480
+ return pending;
481
+ }
482
+ /** Uses a token another process refreshed instead of refreshing again, before and after. */
483
+ async refreshLatest(kek, row, payload, sessionId, url) {
484
+ const before = await this.rotatedSince(kek, row);
485
+ if (before)
486
+ return { status: "ok", payload: before };
487
+ const refreshed = await this.refresh(kek, row, payload, sessionId, url);
488
+ if (refreshed.status === "ok")
489
+ return refreshed;
490
+ // The loser of a race with another process fails with the spent refresh token.
491
+ const after = await this.rotatedSince(kek, row);
492
+ return after ? { status: "ok", payload: after } : refreshed;
493
+ }
494
+ /** The credential's payload when it was rotated after `row` was read and is not due. */
495
+ async rotatedSince(kek, row) {
496
+ const current = await this.store.tx((t) => t.getCredential(row.id));
497
+ if (!current || current.vaultId !== row.vaultId || !isUserCredential(current))
498
+ return undefined;
499
+ if (current.rotatedAt === row.rotatedAt || dueForRefresh(current.expiresAt))
500
+ return undefined;
501
+ try {
502
+ return readPayload(kek, current);
503
+ }
504
+ catch {
505
+ return undefined;
506
+ }
507
+ }
514
508
  async refresh(kek, row, payload, sessionId, url) {
515
509
  const failRefresh = async () => {
516
510
  await this.store.tx((t) => this.audit(t, {
@@ -557,6 +551,7 @@ export class VaultService {
557
551
  headers,
558
552
  body: body.toString(),
559
553
  redirect: "error",
554
+ signal: AbortSignal.timeout(this.refreshTimeoutMs),
560
555
  });
561
556
  if (!response.ok)
562
557
  return failRefresh();
@@ -640,6 +635,8 @@ export class VaultService {
640
635
  });
641
636
  }
642
637
  validateHostModel(body) {
638
+ if (body.settings && body.provider !== "custom")
639
+ throw new VaultError(400, "Model settings (context window, output limit, reasoning, compat) apply only to a custom provider.");
643
640
  if (body.provider === "custom") {
644
641
  if (!body.baseUrl)
645
642
  throw new VaultError(400, "A base URL is required for a custom provider.");
@@ -673,6 +670,9 @@ export class VaultService {
673
670
  provider: input.provider,
674
671
  model: input.model,
675
672
  ...(input.baseUrl ? { baseUrl: input.baseUrl } : {}),
673
+ ...(input.settings && Object.keys(input.settings).length
674
+ ? { settings: input.settings }
675
+ : {}),
676
676
  authType: input.authType,
677
677
  }),
678
678
  expiresAt: null,
@@ -682,7 +682,7 @@ export class VaultService {
682
682
  });
683
683
  }
684
684
  async hostModelView(t) {
685
- const row = await this.activeHostCredentialRow(t);
685
+ const row = await activeHostCredentialRow(t);
686
686
  if (!row)
687
687
  return { configured: false };
688
688
  const binding = modelBinding(row);
@@ -692,67 +692,15 @@ export class VaultService {
692
692
  model: binding.model,
693
693
  authType: binding.authType,
694
694
  ...(binding.baseUrl ? { baseUrl: binding.baseUrl } : {}),
695
+ ...(binding.settings ? { settings: binding.settings } : {}),
695
696
  };
696
697
  }
697
- async hostModelRows(t) {
698
- return (await t.credentialsForVault(HOST_VAULT_ID, { type: "model" })).filter((row) => {
699
- try {
700
- modelBinding(row);
701
- return true;
702
- }
703
- catch {
704
- return false;
705
- }
706
- });
707
- }
708
- async hostCredentialRowFor(t, provider) {
709
- const preferred = hostModelCredentialId(provider);
710
- const rows = await this.hostModelRows(t);
711
- return (rows.find((row) => row.id === preferred) ??
712
- rows.find((row) => modelBinding(row).provider === provider));
713
- }
714
- async activeHostCredentialRow(t) {
715
- const active = await this.activeProviderId(t);
716
- if (!active)
717
- return undefined;
718
- return this.hostCredentialRowFor(t, active);
719
- }
720
- async activeProviderId(t) {
721
- const vault = await t.getVault(HOST_VAULT_ID);
722
- if (vault?.metadataJson) {
723
- let activeProvider;
724
- try {
725
- activeProvider = JSON.parse(vault.metadataJson).activeProvider;
726
- }
727
- catch {
728
- /* Fall through to the only configured provider. */
729
- }
730
- if (typeof activeProvider === "string" && activeProvider) {
731
- try {
732
- if (await this.hostCredentialRowFor(t, activeProvider))
733
- return activeProvider;
734
- }
735
- catch {
736
- /* An invalid provider id falls through, as before. */
737
- }
738
- }
739
- }
740
- const rows = await this.hostModelRows(t);
741
- if (rows.length === 0)
742
- return undefined;
743
- if (rows.length === 1)
744
- return modelBinding(rows[0]).provider;
745
- const legacy = rows.find((row) => row.id === HOST_MODEL_ID);
746
- if (legacy)
747
- return modelBinding(legacy).provider;
748
- return modelBinding(rows[0]).provider;
749
- }
750
698
  async setActiveProvider(t, provider) {
751
699
  await this.ensureHostVault(t);
752
700
  await t.updateVaultMetadata(HOST_VAULT_ID, JSON.stringify({ activeProvider: provider }));
753
701
  }
754
702
  async deleteHostProviderRows(t, provider) {
755
- const ids = (await this.hostModelRows(t))
703
+ const ids = (await hostModelRows(t))
756
704
  .filter((row) => modelBinding(row).provider === provider)
757
705
  .map((row) => row.id);
758
706
  ids.push(hostModelCredentialId(provider));
@@ -818,7 +766,64 @@ function readPayload(kek, row) {
818
766
  plaintext.fill(0);
819
767
  }
820
768
  }
821
- function readHostPayload(kek, row, binding) {
769
+ /** The host vault's model credential rows that parse. */
770
+ export async function hostModelRows(t) {
771
+ return (await t.credentialsForVault(HOST_VAULT_ID, { type: "model" })).filter((row) => {
772
+ try {
773
+ modelBinding(row);
774
+ return true;
775
+ }
776
+ catch {
777
+ return false;
778
+ }
779
+ });
780
+ }
781
+ /** The host model credential of `provider`. */
782
+ export async function hostCredentialRowFor(t, provider) {
783
+ const preferred = hostModelCredentialId(provider);
784
+ const rows = await hostModelRows(t);
785
+ return (rows.find((row) => row.id === preferred) ??
786
+ rows.find((row) => modelBinding(row).provider === provider));
787
+ }
788
+ /** The provider the host model selection names, or the only (or legacy) one configured. */
789
+ export async function activeProviderId(t) {
790
+ const vault = await t.getVault(HOST_VAULT_ID);
791
+ if (vault?.metadataJson) {
792
+ let activeProvider;
793
+ try {
794
+ activeProvider = JSON.parse(vault.metadataJson).activeProvider;
795
+ }
796
+ catch {
797
+ /* Fall through to the only configured provider. */
798
+ }
799
+ if (typeof activeProvider === "string" && activeProvider) {
800
+ try {
801
+ if (await hostCredentialRowFor(t, activeProvider))
802
+ return activeProvider;
803
+ }
804
+ catch {
805
+ /* An invalid provider id falls through, as before. */
806
+ }
807
+ }
808
+ }
809
+ const rows = await hostModelRows(t);
810
+ if (rows.length === 0)
811
+ return undefined;
812
+ if (rows.length === 1)
813
+ return modelBinding(rows[0]).provider;
814
+ const legacy = rows.find((row) => row.id === HOST_MODEL_ID);
815
+ if (legacy)
816
+ return modelBinding(legacy).provider;
817
+ return modelBinding(rows[0]).provider;
818
+ }
819
+ /** The active host model credential row (ciphertext), in `t`. */
820
+ export async function activeHostCredentialRow(t) {
821
+ const active = await activeProviderId(t);
822
+ if (!active)
823
+ return undefined;
824
+ return hostCredentialRowFor(t, active);
825
+ }
826
+ export function readHostPayload(kek, row, binding) {
822
827
  const plaintext = decryptSecret(kek, hostModelAad(binding.provider, binding.model), Buffer.from(row.nonce), Buffer.from(row.ciphertext), Buffer.from(row.wrappedDek), row.kekId);
823
828
  try {
824
829
  return JSON.parse(plaintext.toString("utf8"));
@@ -889,7 +894,7 @@ function requireTimestamp(value) {
889
894
  throw new VaultError(400, "Credential expiry is invalid");
890
895
  return new Date(Date.parse(value)).toISOString();
891
896
  }
892
- function modelBinding(row) {
897
+ export function modelBinding(row) {
893
898
  const binding = JSON.parse(row.bindingJson);
894
899
  if (!binding.provider || !binding.model || !binding.authType)
895
900
  throw new VaultError(500, "Host model credential is unreadable");
@@ -900,7 +905,7 @@ function hostModelCredentialId(provider) {
900
905
  throw new VaultError(400, "Invalid provider id");
901
906
  return `${HOST_MODEL_ID}:${provider}`;
902
907
  }
903
- function hostModelAad(provider, model) {
908
+ export function hostModelAad(provider, model) {
904
909
  return Buffer.from(canonical({
905
910
  scope: "host",
906
911
  vaultId: HOST_VAULT_ID,
package/dist/version.d.ts CHANGED
@@ -1 +1 @@
1
- export declare const RUNTIME_VERSION = "0.13.0-beta";
1
+ export declare const RUNTIME_VERSION = "0.15.0-beta";
package/dist/version.js CHANGED
@@ -1,3 +1,3 @@
1
1
  // Kept in step with package.json by runtime/scripts/check-package.mjs, which runs in npm run check.
2
2
  // release:prepare also rewrites this when it bumps @nylorun/runtime.
3
- export const RUNTIME_VERSION = "0.13.0-beta";
3
+ export const RUNTIME_VERSION = "0.15.0-beta";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nylorun/runtime",
3
- "version": "0.13.0-beta",
3
+ "version": "0.15.0-beta",
4
4
  "description": "Nylorun Runtime: the agent execution Host on Postgres, Restate and S2, and model providers.",
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
@@ -30,6 +30,8 @@
30
30
  "types": "./dist/configuration.d.ts",
31
31
  "import": "./dist/configuration.js"
32
32
  },
33
+ "./openapi.json": "./dist/openapi.json",
34
+ "./admin-openapi.json": "./dist/admin-openapi.json",
33
35
  "./package.json": "./package.json"
34
36
  },
35
37
  "files": [
@@ -43,7 +45,7 @@
43
45
  "provenance": true
44
46
  },
45
47
  "scripts": {
46
- "build": "npm run clean && node ../scripts/check-typescript-version.mjs && tsc -p tsconfig.json",
48
+ "build": "npm run clean && node ../scripts/check-typescript-version.mjs && tsc -p tsconfig.json && node scripts/copy-migrations.mjs && node scripts/build-openapi.mjs",
47
49
  "check": "npm run build && npm test && node scripts/check-package.mjs",
48
50
  "typecheck": "node ../scripts/check-typescript-version.mjs && tsc -p tsconfig.json --noEmit",
49
51
  "test": "vitest run",
@@ -52,24 +54,38 @@
52
54
  "test:stack:down": "node test/stack/up.mjs down",
53
55
  "prepack": "npm run build",
54
56
  "clean": "node -e \"import('node:fs').then(({rmSync})=>rmSync('dist',{recursive:true,force:true}))\"",
55
- "start": "node dist/host/main.js"
57
+ "start": "node dist/host/main.js",
58
+ "db:generate": "drizzle-kit generate",
59
+ "db:studio": "drizzle-kit studio"
56
60
  },
57
61
  "dependencies": {
58
- "@modelcontextprotocol/sdk": "^1.30.0",
59
- "@earendil-works/pi-ai": "0.87.0",
62
+ "@ag-ui/core": "~1.0.0",
63
+ "@asteasolutions/zod-to-openapi": "^9.1.0",
64
+ "@earendil-works/pi-ai": "0.99.1",
60
65
  "@hono/node-server": "^2.1.1",
61
- "@nylorun/harness": "0.20.0-beta",
62
- "@nylorun/core": "0.8.0-beta",
66
+ "@hono/zod-openapi": "^1.6.3",
67
+ "@modelcontextprotocol/sdk": "^1.30.0",
68
+ "@nylorun/core": "0.10.0-beta",
69
+ "@nylorun/harness": "0.21.1-beta",
63
70
  "@restatedev/restate-sdk": "1.16.9",
64
71
  "@s2-dev/streamstore": "0.27.0",
72
+ "drizzle-orm": "^0.45.3",
73
+ "hono": "^4.13.12",
74
+ "jose": "^6.2.12",
65
75
  "just-bash": "3.4.2",
66
- "postgres": "^3.4.9"
76
+ "pg": "8.23.1",
77
+ "pg-logical-replication": "2.5.0",
78
+ "postgres": "^3.4.9",
79
+ "zod": "^4.6.5"
67
80
  },
68
81
  "devDependencies": {
82
+ "@a2a-js/sdk": "~1.3.0",
69
83
  "@ag-ui/client": "1.0.0",
84
+ "@scalar/openapi-parser": "^0.29.9",
70
85
  "@types/node": "^26.5.0",
86
+ "@types/pg": "^8.23.1",
71
87
  "@typescript/native": "npm:typescript@^7.0.2",
72
- "hono": "^4.13.8",
88
+ "drizzle-kit": "^0.31.11",
73
89
  "typescript": "npm:@typescript/typescript6@^6.0.2",
74
90
  "vitest": "^5.0.1"
75
91
  }
@@ -1,44 +0,0 @@
1
- import type { ExecutorScope } from "@nylorun/core/contracts";
2
- import type { SessionStore } from "../store/types.js";
3
- /**
4
- * Executor bearer tokens are 256-bit CSPRNG values minted by the CLI and the wire schema
5
- * requires at least 16 characters, so an unsalted digest is adequate to keep them out of
6
- * the Session Store at rest. This is not a password KDF and must not be used for one.
7
- */
8
- export declare const hashToken: (token: string) => string;
9
- export interface ExecutorRecord extends ExecutorScope {
10
- readonly tokenHash: string;
11
- /** Always true under Runtime Tenants; startup env executors are removed (D8). */
12
- readonly persisted: boolean;
13
- readonly updatedAt: string;
14
- /** Application principal that last registered this executor, when known. */
15
- readonly principalId?: string;
16
- }
17
- /**
18
- * Rejects tokens that are weak, empty of agent identity, or whose hash equals any
19
- * application principal hash (A5). Replaces the old `serverToken` equality check.
20
- */
21
- export declare function assertExecutorCredential(executor: {
22
- agentId: string;
23
- implementationVersion: string;
24
- token: string;
25
- }, applicationHashes: readonly string[]): void;
26
- /** Mint a 32-byte hex application/executor key for tests and CLI bootstrap. */
27
- export declare function mintBearerToken(): string;
28
- export declare class ExecutorRegistry {
29
- private readonly byHash;
30
- private readonly byAgent;
31
- seed(records: readonly ExecutorRecord[]): void;
32
- find(tokenHash: string): ExecutorRecord | undefined;
33
- get(agentId: string): ExecutorRecord | undefined;
34
- list(): readonly ExecutorRecord[];
35
- upsert(record: ExecutorRecord): {
36
- rotated: boolean;
37
- previousHash?: string;
38
- previousPrincipalId?: string;
39
- };
40
- remove(agentId: string): ExecutorRecord | undefined;
41
- private write;
42
- }
43
- /** A registry seeded from the executors the store persisted (at Tenant open). */
44
- export declare function loadExecutorRegistry(store: SessionStore): Promise<ExecutorRegistry>;
@@ -1,95 +0,0 @@
1
- import { createHash, randomBytes, timingSafeEqual } from "node:crypto";
2
- const equals = (a, b) => {
3
- const aa = Buffer.from(a), bb = Buffer.from(b);
4
- return aa.length === bb.length && timingSafeEqual(aa, bb);
5
- };
6
- /**
7
- * Executor bearer tokens are 256-bit CSPRNG values minted by the CLI and the wire schema
8
- * requires at least 16 characters, so an unsalted digest is adequate to keep them out of
9
- * the Session Store at rest. This is not a password KDF and must not be used for one.
10
- */
11
- export const hashToken = (token) => createHash("sha256").update(token, "utf8").digest("hex");
12
- /**
13
- * Rejects tokens that are weak, empty of agent identity, or whose hash equals any
14
- * application principal hash (A5). Replaces the old `serverToken` equality check.
15
- */
16
- export function assertExecutorCredential(executor, applicationHashes) {
17
- const tokenHash = hashToken(executor.token);
18
- const matchesApplication = applicationHashes.some((hash) => equals(hash, tokenHash));
19
- if (executor.token.length < 16 ||
20
- matchesApplication ||
21
- !executor.agentId ||
22
- !executor.implementationVersion)
23
- throw new Error("Executor tokens require independent credentials and an agent id");
24
- }
25
- /** Mint a 32-byte hex application/executor key for tests and CLI bootstrap. */
26
- export function mintBearerToken() {
27
- return randomBytes(32).toString("hex");
28
- }
29
- export class ExecutorRegistry {
30
- byHash = new Map();
31
- byAgent = new Map();
32
- seed(records) {
33
- for (const record of records)
34
- this.write(record);
35
- }
36
- find(tokenHash) {
37
- return this.byHash.get(tokenHash);
38
- }
39
- get(agentId) {
40
- return this.byAgent.get(agentId);
41
- }
42
- list() {
43
- return [...this.byAgent.values()];
44
- }
45
- upsert(record) {
46
- const previous = this.byAgent.get(record.agentId);
47
- const collision = this.byHash.get(record.tokenHash);
48
- if (collision && collision.agentId !== record.agentId)
49
- throw new Error("Executor tokens must be unique");
50
- this.write(record);
51
- if (!previous || previous.tokenHash === record.tokenHash)
52
- return { rotated: false };
53
- return {
54
- rotated: true,
55
- previousHash: previous.tokenHash,
56
- ...(previous.principalId === undefined
57
- ? {}
58
- : { previousPrincipalId: previous.principalId }),
59
- };
60
- }
61
- remove(agentId) {
62
- const record = this.byAgent.get(agentId);
63
- if (!record)
64
- return undefined;
65
- this.byAgent.delete(agentId);
66
- this.byHash.delete(record.tokenHash);
67
- return record;
68
- }
69
- write(record) {
70
- const previous = this.byAgent.get(record.agentId);
71
- if (previous)
72
- this.byHash.delete(previous.tokenHash);
73
- this.byAgent.set(record.agentId, record);
74
- this.byHash.set(record.tokenHash, record);
75
- }
76
- }
77
- /** A registry seeded from the executors the store persisted (at Tenant open). */
78
- export async function loadExecutorRegistry(store) {
79
- const registry = new ExecutorRegistry();
80
- const rows = await store.tx((t) => t.listExecutors());
81
- registry.seed(rows.map((row) => ({
82
- agentId: row.agentId,
83
- implementationVersion: row.implementationVersion,
84
- ...(row.manifestHash === undefined
85
- ? {}
86
- : { manifestHash: row.manifestHash }),
87
- tokenHash: row.tokenHash,
88
- persisted: true,
89
- updatedAt: row.updatedAt,
90
- ...(row.principalId === undefined
91
- ? {}
92
- : { principalId: row.principalId }),
93
- })));
94
- return registry;
95
- }
@@ -1,57 +0,0 @@
1
- import { type CommitListener, type DocTable, type ExecutorRow, type PrincipalRow, type SessionOwnership, type SessionStore, type SessionStoreOptions, type StoreHealth, type Tx, type VaultAuditRow, type VaultCredentialRow, type VaultIdempotencyRow, type VaultRow } from "./types.js";
2
- /** Schema version the fake reports; it has no migrations. */
3
- export declare const MEMORY_SCHEMA_VERSION = 1;
4
- interface SessionMeta extends SessionOwnership {
5
- nextSeq: number;
6
- }
7
- interface State {
8
- /** JSON text per id, like a `body` column. */
9
- docs: Record<DocTable, Map<string, string>>;
10
- sessionMeta: Map<string, SessionMeta>;
11
- /** sessionId → seq → event JSON. */
12
- outbox: Map<string, Map<number, string>>;
13
- executors: Map<string, ExecutorRow>;
14
- principals: Map<string, PrincipalRow>;
15
- vaults: Map<string, VaultRow>;
16
- credentials: Map<string, VaultCredentialRow>;
17
- audit: VaultAuditRow[];
18
- idempotency: Map<string, VaultIdempotencyRow>;
19
- settings: Map<string, string>;
20
- }
21
- /**
22
- * The data behind in-memory Session Stores: what a database is to its connections. Several
23
- * `MemorySessionStore`s on one `MemoryStoreData` see each other's commits and serialize their
24
- * transactions together, as several processes on one Tenant schema would; it outlives them,
25
- * so a test can close a Tenant and open it again.
26
- */
27
- export declare class MemoryStoreData {
28
- /** @internal */ state: State;
29
- /** @internal */ queue: Promise<void>;
30
- }
31
- /**
32
- * In-memory `SessionStore` for unit tests. Not a supported profile.
33
- *
34
- * Transactions are serialized (one at a time), each works on a copy of the
35
- * state and replaces it on commit, so a throw rolls back everything: documents,
36
- * sequences, outbox rows, `afterCommit` callbacks and `signalWork`. Commit listeners see
37
- * only this store's commits, as with a Postgres connection.
38
- */
39
- export declare class MemorySessionStore implements SessionStore {
40
- /** Shared with other stores on the same data; a fresh, private one by default. */
41
- private readonly data;
42
- readonly tenantId: string;
43
- private readonly active;
44
- private readonly listeners;
45
- private readonly now;
46
- private readonly onError;
47
- private closed;
48
- constructor(options: SessionStoreOptions,
49
- /** Shared with other stores on the same data; a fresh, private one by default. */
50
- data?: MemoryStoreData);
51
- tx<T>(fn: (t: Tx) => Promise<T>): Promise<T>;
52
- onCommit(listener: CommitListener): () => void;
53
- health(): Promise<StoreHealth>;
54
- close(): Promise<void>;
55
- private acquire;
56
- }
57
- export {};