@nylorun/runtime 0.17.0-beta → 0.19.1-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 (223) hide show
  1. package/CHANGELOG.md +155 -0
  2. package/README.md +89 -58
  3. package/dist/adapters/execution/restate.d.ts +1 -3
  4. package/dist/adapters/execution/restate.js +4 -31
  5. package/dist/adapters/replication/pgoutput.js +1 -1
  6. package/dist/adapters/sandbox/local.js +1 -1
  7. package/dist/adapters/sandbox/virtual.js +1 -1
  8. package/dist/api/ag-ui/routes.js +1 -1
  9. package/dist/api/ag-ui/translate.d.ts +1 -3
  10. package/dist/api/ag-ui/translate.js +2 -17
  11. package/dist/api/components.d.ts +4871 -2614
  12. package/dist/api/components.js +12 -11
  13. package/dist/api/gate/routes.d.ts +7 -8
  14. package/dist/api/gate/routes.js +58 -52
  15. package/dist/api/http/app.d.ts +6 -0
  16. package/dist/api/http/app.js +18 -14
  17. package/dist/api/http/define.d.ts +14 -2
  18. package/dist/api/http/define.js +34 -15
  19. package/dist/api/http/routes/access.d.ts +3 -3
  20. package/dist/api/http/routes/access.js +18 -17
  21. package/dist/api/http/routes/artifacts.js +2 -4
  22. package/dist/api/http/routes/files.d.ts +8 -0
  23. package/dist/api/http/routes/files.js +77 -0
  24. package/dist/api/http/routes/keys.d.ts +10 -0
  25. package/dist/api/http/routes/keys.js +64 -0
  26. package/dist/api/http/routes/me.js +5 -2
  27. package/dist/api/http/routes/reads.d.ts +66 -0
  28. package/dist/api/http/routes/reads.js +127 -0
  29. package/dist/api/http/routes/sandboxes.js +7 -4
  30. package/dist/api/http/routes/sessions.js +25 -8
  31. package/dist/api/http/routes/tenant.js +15 -18
  32. package/dist/api/http/routes/vaults.d.ts +10 -9
  33. package/dist/api/http/routes/vaults.js +27 -23
  34. package/dist/api/openapi.d.ts +10 -11
  35. package/dist/api/openapi.js +288 -63
  36. package/dist/artifacts/links.js +1 -1
  37. package/dist/core/flow-host.d.ts +44 -23
  38. package/dist/core/flow-host.js +88 -106
  39. package/dist/core/provider.d.ts +4 -1
  40. package/dist/core/provider.js +6 -1
  41. package/dist/core/sandbox-routes.d.ts +0 -9
  42. package/dist/core/sandbox-routes.js +2 -21
  43. package/dist/execution/memory.d.ts +4 -5
  44. package/dist/execution/memory.js +11 -25
  45. package/dist/execution/types.d.ts +3 -33
  46. package/dist/execution/types.js +2 -12
  47. package/dist/gates/http-tool.d.ts +86 -0
  48. package/dist/gates/http-tool.js +197 -0
  49. package/dist/gates/mcp-handler.d.ts +3 -0
  50. package/dist/gates/mcp-handler.js +1 -3
  51. package/dist/gates/meter.d.ts +2 -2
  52. package/dist/gates/meter.js +6 -0
  53. package/dist/gates/tenant-vaults.d.ts +5 -9
  54. package/dist/gates/tool-calls.d.ts +7 -15
  55. package/dist/gates/tool-calls.js +4 -4
  56. package/dist/gates/tool-client.d.ts +1 -1
  57. package/dist/gates/tool-client.js +23 -47
  58. package/dist/gates/tool-contract.d.ts +27 -39
  59. package/dist/gates/tool-contract.js +30 -21
  60. package/dist/gates/tool-gate.d.ts +25 -24
  61. package/dist/gates/tool-gate.js +11 -7
  62. package/dist/harness/calls.d.ts +10 -4
  63. package/dist/harness/calls.js +15 -6
  64. package/dist/harness/executors.d.ts +1 -1
  65. package/dist/harness/executors.js +17 -12
  66. package/dist/harness/main.d.ts +2 -2
  67. package/dist/harness/main.js +5 -13
  68. package/dist/harness/service.d.ts +4 -5
  69. package/dist/harness/service.js +9 -2
  70. package/dist/harness-api/in-process.js +2 -2
  71. package/dist/harness-api/record.d.ts +6 -3
  72. package/dist/harness-api/record.js +65 -72
  73. package/dist/harness-api/server.d.ts +1 -9
  74. package/dist/harness-api/server.js +19 -17
  75. package/dist/harness-api/start.js +1 -5
  76. package/dist/harness-api/workspace.js +2 -3
  77. package/dist/harness-api/ws-server.js +1 -1
  78. package/dist/host/app.d.ts +2 -9
  79. package/dist/host/app.js +15 -25
  80. package/dist/host/config-for.d.ts +1 -5
  81. package/dist/host/config-for.js +0 -3
  82. package/dist/host/config.d.ts +0 -5
  83. package/dist/host/create-host.d.ts +3 -33
  84. package/dist/host/create-host.js +18 -94
  85. package/dist/host/environment.d.ts +0 -3
  86. package/dist/host/environment.js +0 -9
  87. package/dist/host/gates.d.ts +4 -1
  88. package/dist/host/gates.js +3 -2
  89. package/dist/host/http.d.ts +1 -2
  90. package/dist/host/http.js +1 -7
  91. package/dist/host/index.d.ts +2 -2
  92. package/dist/host/index.js +2 -2
  93. package/dist/host/main.js +27 -34
  94. package/dist/host/operate.d.ts +29 -0
  95. package/dist/host/operate.js +223 -0
  96. package/dist/host/stack-config.d.ts +12 -12
  97. package/dist/host/stack-config.js +5 -32
  98. package/dist/index.d.ts +1 -1
  99. package/dist/index.js +1 -1
  100. package/dist/infra/workers.d.ts +1 -1
  101. package/dist/keys/keys.js +2 -2
  102. package/dist/management-openapi.json +6491 -0
  103. package/dist/mcp/connect.d.ts +8 -4
  104. package/dist/mcp/connect.js +6 -26
  105. package/dist/mcp/pool.d.ts +5 -9
  106. package/dist/mcp/pool.js +8 -19
  107. package/dist/mcp/snapshot.d.ts +8 -2
  108. package/dist/mcp/snapshot.js +29 -2
  109. package/dist/media.d.ts +1 -1
  110. package/dist/media.js +1 -1
  111. package/dist/model/pi-model.js +3 -0
  112. package/dist/openapi.json +9372 -17030
  113. package/dist/reads/access.d.ts +4 -0
  114. package/dist/reads/access.js +12 -0
  115. package/dist/reads/cursor.d.ts +11 -0
  116. package/dist/reads/cursor.js +80 -0
  117. package/dist/reads/types.d.ts +25 -0
  118. package/dist/reads/types.js +1 -0
  119. package/dist/sandbox/manager.d.ts +16 -0
  120. package/dist/sandbox/manager.js +40 -0
  121. package/dist/sandbox/session-sandbox.js +13 -3
  122. package/dist/sandbox/skills.d.ts +13 -0
  123. package/dist/sandbox/skills.js +46 -0
  124. package/dist/sandbox/types.d.ts +2 -2
  125. package/dist/store/ownership.d.ts +9 -0
  126. package/dist/store/ownership.js +13 -0
  127. package/dist/store/postgres/connect.d.ts +2 -0
  128. package/dist/store/postgres/connect.js +29 -0
  129. package/dist/store/postgres/drizzle/0011_key_roles.sql +3 -0
  130. package/dist/store/postgres/drizzle/0012_session_reads.sql +8 -0
  131. package/dist/store/postgres/drizzle/0013_definition_files.sql +16 -0
  132. package/dist/store/postgres/drizzle/0014_action_endpoints_removed.sql +2 -0
  133. package/dist/store/postgres/drizzle/meta/_journal.json +28 -0
  134. package/dist/store/postgres/reads.d.ts +3 -0
  135. package/dist/store/postgres/reads.js +272 -0
  136. package/dist/store/postgres/schema.d.ts +294 -489
  137. package/dist/store/postgres/schema.js +41 -76
  138. package/dist/store/postgres/store.js +51 -154
  139. package/dist/store/postgres/tenant.d.ts +34 -2
  140. package/dist/store/postgres/tenant.js +16 -32
  141. package/dist/store/types.d.ts +27 -96
  142. package/dist/store/types.js +0 -1
  143. package/dist/streams/control.d.ts +0 -2
  144. package/dist/streams/control.js +0 -5
  145. package/dist/streams/types.d.ts +1 -10
  146. package/dist/tenant/advance.d.ts +3 -3
  147. package/dist/tenant/advance.js +10 -7
  148. package/dist/tenant/auth.d.ts +14 -9
  149. package/dist/tenant/auth.js +24 -28
  150. package/dist/tenant/cause.d.ts +3 -3
  151. package/dist/tenant/cause.js +1 -1
  152. package/dist/tenant/commands.d.ts +2 -18
  153. package/dist/tenant/commands.js +35 -172
  154. package/dist/tenant/context.d.ts +14 -24
  155. package/dist/tenant/definition-files.d.ts +24 -0
  156. package/dist/tenant/definition-files.js +103 -0
  157. package/dist/tenant/effects.d.ts +18 -20
  158. package/dist/tenant/effects.js +56 -119
  159. package/dist/tenant/ephemeral.d.ts +7 -6
  160. package/dist/tenant/ephemeral.js +3 -11
  161. package/dist/tenant/host-token.js +1 -1
  162. package/dist/tenant/jwt.js +1 -1
  163. package/dist/tenant/module.d.ts +1 -4
  164. package/dist/tenant/module.js +3 -12
  165. package/dist/tenant/operator-keys.d.ts +34 -8
  166. package/dist/tenant/operator-keys.js +50 -18
  167. package/dist/tenant/outbound.d.ts +10 -1
  168. package/dist/tenant/outbound.js +44 -27
  169. package/dist/tenant/paths.d.ts +1 -1
  170. package/dist/tenant/paths.js +1 -3
  171. package/dist/tenant/principals.d.ts +11 -2
  172. package/dist/tenant/principals.js +27 -8
  173. package/dist/tenant/run-token.js +3 -3
  174. package/dist/tenant/runtime.d.ts +4 -6
  175. package/dist/tenant/runtime.js +19 -18
  176. package/dist/tenant/sandboxes.d.ts +4 -1
  177. package/dist/tenant/sandboxes.js +11 -0
  178. package/dist/tenant/scheduler.d.ts +1 -5
  179. package/dist/tenant/scheduler.js +2 -5
  180. package/dist/tenant/session-streams.d.ts +2 -1
  181. package/dist/tenant/session-streams.js +39 -5
  182. package/dist/tenant/session.d.ts +8 -41
  183. package/dist/tenant/session.js +0 -37
  184. package/dist/tenant/sessions.js +16 -8
  185. package/dist/tenant/signing-keys.d.ts +3 -3
  186. package/dist/tenant/signing-keys.js +5 -5
  187. package/dist/tenant/skill-tool.d.ts +20 -0
  188. package/dist/tenant/skill-tool.js +72 -0
  189. package/dist/tenant/slim.d.ts +1 -1
  190. package/dist/tenant/slim.js +1 -1
  191. package/dist/tenant/status.js +7 -12
  192. package/dist/tenant/store-pg.d.ts +15 -0
  193. package/dist/tenant/store-pg.js +7 -3
  194. package/dist/tenant/streams.d.ts +3 -10
  195. package/dist/tenant/streams.js +1 -24
  196. package/dist/tenant/sweep.js +0 -5
  197. package/dist/tenant/transcript.d.ts +2 -2
  198. package/dist/tenant/transcript.js +2 -2
  199. package/dist/tenant/types.d.ts +15 -22
  200. package/dist/tenant/worker.d.ts +1 -3
  201. package/dist/tenant/worker.js +3 -8
  202. package/dist/vault/sources.d.ts +7 -1
  203. package/dist/vault/sources.js +22 -11
  204. package/dist/version.d.ts +1 -1
  205. package/dist/version.js +1 -1
  206. package/package.json +4 -4
  207. package/dist/admin-openapi.json +0 -904
  208. package/dist/api/http/routes/actions.d.ts +0 -8
  209. package/dist/api/http/routes/actions.js +0 -75
  210. package/dist/api/http/routes/endpoints.d.ts +0 -7
  211. package/dist/api/http/routes/endpoints.js +0 -100
  212. package/dist/host/admin-api.d.ts +0 -30
  213. package/dist/host/admin-api.js +0 -140
  214. package/dist/mcp/stdio.d.ts +0 -17
  215. package/dist/mcp/stdio.js +0 -82
  216. package/dist/tenant/actions.d.ts +0 -14
  217. package/dist/tenant/actions.js +0 -29
  218. package/dist/tenant/delivery-token.d.ts +0 -41
  219. package/dist/tenant/delivery-token.js +0 -104
  220. package/dist/tenant/delivery.d.ts +0 -62
  221. package/dist/tenant/delivery.js +0 -481
  222. package/dist/tenant/endpoints.d.ts +0 -67
  223. package/dist/tenant/endpoints.js +0 -90
@@ -0,0 +1,77 @@
1
+ import { z } from "zod";
2
+ import { DEFINITION_FILE_MAX_BYTES } from "@nylorun/core/contracts";
3
+ import { DefinitionFileView } from "../../components.js";
4
+ import { normalizeContentType } from "../../../artifacts/media-types.js";
5
+ import { hasDefinitionFile, putDefinitionFile } from "../../../tenant/definition-files.js";
6
+ import { fail } from "../../../tenant/http.js";
7
+ import { tenantRoute } from "../define.js";
8
+ import { jsonResponse } from "../respond.js";
9
+ const APPLICATION = { credentials: ["application"], scopes: "never" };
10
+ const UPLOAD = { ...APPLICATION, bytes: true };
11
+ const file = z.object({
12
+ file: z.string().meta({ description: "`sha256:` and the SHA-256 of the file's bytes, 64 lowercase hex digits" }),
13
+ });
14
+ const view = (description) => ({
15
+ description,
16
+ content: { "application/json": { schema: DefinitionFileView } },
17
+ });
18
+ /** The declared `Content-Length`, when there is one. */
19
+ function declaredLength(request) {
20
+ const raw = request.headers.get("content-length");
21
+ return raw !== null && /^\d+$/.test(raw) ? Number(raw) : undefined;
22
+ }
23
+ /** The declared media type, unless it is absent or `application/octet-stream`. */
24
+ function declaredType(request) {
25
+ const raw = request.headers.get("content-type") ?? undefined;
26
+ if (raw === undefined)
27
+ return undefined;
28
+ const type = normalizeContentType(raw);
29
+ if (type === undefined)
30
+ return fail(400, "Content-Type is not a media type");
31
+ return type.toLowerCase().startsWith("application/octet-stream") ? undefined : type;
32
+ }
33
+ export function fileRoutes(api) {
34
+ tenantRoute(api, UPLOAD, {
35
+ method: "put",
36
+ path: "/v1/files/{file}",
37
+ tags: ["Definition files"],
38
+ summary: "Upload a definition file",
39
+ description: `Stores the body, at most ${DEFINITION_FILE_MAX_BYTES} bytes, at the SHA-256 the path names; a body with another hash stores nothing (\`400\`). A file the Tenant holds already is kept as it is (\`200\`).`,
40
+ request: {
41
+ params: file,
42
+ body: {
43
+ required: true,
44
+ description: "The file's bytes",
45
+ content: { "application/octet-stream": { schema: z.string().meta({ format: "binary" }) } },
46
+ },
47
+ },
48
+ responses: {
49
+ 200: view("The Tenant held the file already"),
50
+ 201: view("The file, stored"),
51
+ 413: { description: `Larger than ${DEFINITION_FILE_MAX_BYTES} bytes (\`limit_exceeded\`)` },
52
+ },
53
+ }, async (c) => {
54
+ const request = c.req.raw;
55
+ const length = declaredLength(request);
56
+ const type = declaredType(request);
57
+ const stored = await putDefinitionFile(c.env.tenant, c.req.param("file"), {
58
+ body: request.body,
59
+ ...(length === undefined ? {} : { declaredBytes: length }),
60
+ ...(type === undefined ? {} : { contentType: type }),
61
+ }, request.signal);
62
+ return jsonResponse(stored.status, stored.view);
63
+ });
64
+ tenantRoute(api, APPLICATION, {
65
+ method: "head",
66
+ path: "/v1/files/{file}",
67
+ tags: ["Definition files"],
68
+ summary: "Whether the Runtime holds a definition file",
69
+ description: "`200` when the Tenant holds the file, `404` when it does not: upload it.",
70
+ request: { params: file },
71
+ responses: { 200: { description: "The Tenant holds the file" } },
72
+ }, async (c) => {
73
+ if (!(await hasDefinitionFile(c.env.tenant, c.req.param("file"))))
74
+ return fail(404, "The Runtime does not hold this file");
75
+ return new Response(null, { status: 200 });
76
+ });
77
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Application keys (`/v1/tenant/keys`, the Management API, protocol 8): a management key
3
+ * creates, rotates, lists and deletes the Tenant's application keys by name. It never touches a
4
+ * management key, `studio` or `bootstrap`: those come only from the Tenant's machine
5
+ * (`nylorun-operate`, `NYLORUN_MANAGEMENT_KEY_FILE`), so a leaked management key cannot mint
6
+ * another. A rotated or deleted key stops authenticating on its next request.
7
+ */
8
+ import type { OpenAPIHono } from "@hono/zod-openapi";
9
+ import type { TenantEnv } from "../app.js";
10
+ export declare function keyRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,64 @@
1
+ import { z } from "zod";
2
+ import { DeleteOperatorKeyResponse, ListOperatorKeysResponse, PutOperatorKeyResponse, } from "../../components.js";
3
+ import { fail } from "../../../tenant/http.js";
4
+ import { operatorKeys } from "../../../tenant/operator-keys.js";
5
+ import { tenantRoute } from "../define.js";
6
+ import { jsonResponse } from "../respond.js";
7
+ const MANAGEMENT = { credentials: ["management"], scopes: "never" };
8
+ const keyId = z.object({
9
+ keyId: z
10
+ .string()
11
+ .meta({ description: "The key's name: `^[a-z][a-z0-9-]{0,31}$`, never `studio` or `bootstrap`" }),
12
+ });
13
+ const json = (schema, description) => ({
14
+ description,
15
+ content: { "application/json": { schema } },
16
+ });
17
+ function refused(refusal) {
18
+ return fail(400, refusal.message, {
19
+ code: refusal.reason === "invalid" ? "invalid_request" : "request_rejected",
20
+ });
21
+ }
22
+ export function keyRoutes(api) {
23
+ tenantRoute(api, MANAGEMENT, {
24
+ method: "get",
25
+ path: "/v1/tenant/keys",
26
+ tags: ["Application keys"],
27
+ summary: "List the Tenant's keys",
28
+ description: "Every key of the Tenant by name, with its role (`application`, `management` or `studio`) and when it was issued. Keys themselves are never shown again.",
29
+ responses: { 200: json(ListOperatorKeysResponse, "The keys, by id") },
30
+ }, async (c) => jsonResponse(200, { keys: await operatorKeys(c.env.tenant.store).list() }));
31
+ tenantRoute(api, MANAGEMENT, {
32
+ method: "put",
33
+ path: "/v1/tenant/keys/{keyId}",
34
+ tags: ["Application keys"],
35
+ summary: "Create or rotate an application key",
36
+ description: "Issues a new application key named `keyId` and returns it this once. An application key of that name is replaced: the old one stops authenticating at once. A management key, `studio` and `bootstrap` are refused.",
37
+ request: { params: keyId },
38
+ responses: {
39
+ 200: json(PutOperatorKeyResponse, "The new key, shown once"),
40
+ 400: { description: "Not a key name, a reserved name, or a management key's name" },
41
+ },
42
+ }, async (c) => {
43
+ const put = await operatorKeys(c.env.tenant.store).put(c.req.param("keyId"), "application");
44
+ return "reason" in put ? refused(put) : jsonResponse(200, put);
45
+ });
46
+ tenantRoute(api, MANAGEMENT, {
47
+ method: "delete",
48
+ path: "/v1/tenant/keys/{keyId}",
49
+ tags: ["Application keys"],
50
+ summary: "Delete an application key",
51
+ description: "The key stops authenticating at once. A management key, `studio` and `bootstrap` are refused.",
52
+ request: { params: keyId },
53
+ responses: {
54
+ 200: json(DeleteOperatorKeyResponse, "The key is deleted"),
55
+ 400: { description: "Not a key name, a reserved name, or a management key's name" },
56
+ },
57
+ }, async (c) => {
58
+ const id = c.req.param("keyId");
59
+ const deleted = await operatorKeys(c.env.tenant.store).delete(id, "application");
60
+ if (typeof deleted !== "boolean")
61
+ return refused(deleted);
62
+ return deleted ? jsonResponse(200, { id, deleted: true }) : fail(404, `No key ${id}`);
63
+ });
64
+ }
@@ -22,18 +22,21 @@ export function describeCaller(scope) {
22
22
  sandboxes: [...(scope.sandboxes ?? [])],
23
23
  via: `issuer:${scope.issuer}`,
24
24
  };
25
+ case "management":
26
+ return { scopes: [], agents: [], via: `management:${scope.principalId}` };
25
27
  default:
26
28
  return fail(403, "This credential acts on no Tenant resource");
27
29
  }
28
30
  }
29
31
  export function meRoutes(api) {
30
- tenantRoute(api, { credentials: ["application", "subject", "token"], scopes: "any" }, {
32
+ tenantRoute(api, { credentials: ["application", "subject", "management", "token"], scopes: "any" }, {
31
33
  method: "get",
32
34
  path: "/v1/me",
33
35
  tags: ["Access"],
34
36
  summary: "Get who the caller is",
35
37
  description: "The subject, scopes, agents and sandbox grants of the credential that sends it: an " +
36
- "application key (alone or acting for a subject), or a trusted issuer's token " +
38
+ "application key (alone or acting for a subject), a management key " +
39
+ "(`via: management:<principalId>`, no scopes, no agents), or a trusted issuer's token " +
37
40
  "(`via: issuer:<name>`).",
38
41
  responses: {
39
42
  200: {
@@ -0,0 +1,66 @@
1
+ /** Read-only sub-resources and opt-in pages. The execution routes retain their contracts. */
2
+ import type { OpenAPIHono } from "@hono/zod-openapi";
3
+ import { z } from "zod";
4
+ import type { TenantEnv } from "../app.js";
5
+ export declare const pageQuery: z.ZodObject<{
6
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
7
+ cursor: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ export declare const sessionPageQuery: z.ZodObject<{
10
+ limit: z.ZodOptional<z.ZodCoercedNumber<unknown>>;
11
+ cursor: z.ZodOptional<z.ZodString>;
12
+ agentId: z.ZodOptional<z.ZodString>;
13
+ status: z.ZodOptional<z.ZodEnum<{
14
+ cancelled: "cancelled";
15
+ completed: "completed";
16
+ failed: "failed";
17
+ idle: "idle";
18
+ paused: "paused";
19
+ runnable: "runnable";
20
+ running: "running";
21
+ uncertain: "uncertain";
22
+ waiting: "waiting";
23
+ }>>;
24
+ sandboxId: z.ZodOptional<z.ZodString>;
25
+ ownerUserId: z.ZodOptional<z.ZodString>;
26
+ }, z.core.$strict>;
27
+ export declare function parseQuery<T>(schema: z.ZodType<T>, query: unknown): T;
28
+ export declare function sessionPage(ctx: TenantEnv["Bindings"]["tenant"], scope: TenantEnv["Variables"]["scope"], query: Record<string, string>): Promise<{
29
+ sessions: {
30
+ id: string;
31
+ agentId: string;
32
+ ownerUserId: string;
33
+ status: "cancelled" | "completed" | "failed" | "idle" | "paused" | "runnable" | "running" | "uncertain" | "waiting";
34
+ activeTurnId: string | null;
35
+ lastTurnId: string | null;
36
+ sandboxId: string | null;
37
+ createdAt: string | null;
38
+ lastEventAt: string | null;
39
+ }[];
40
+ nextCursor: string | null;
41
+ }>;
42
+ export declare function sandboxPage(ctx: TenantEnv["Bindings"]["tenant"], scope: TenantEnv["Variables"]["scope"], query: Record<string, string>, labels: Record<string, string>): Promise<{
43
+ sandboxes: {
44
+ id: string;
45
+ kind: "pod" | "virtual";
46
+ labels: Record<string, string>;
47
+ spec: Record<string, unknown>;
48
+ state: "creating" | "ready" | "running" | "stopped";
49
+ pod?: {
50
+ desired: "deleted" | "running" | "suspended";
51
+ observed: "creating" | "deleting" | "expired" | "failed" | "lost" | "running" | "suspended";
52
+ volumeGeneration: number;
53
+ hostEpoch: number;
54
+ expiresAt?: string | undefined;
55
+ reason?: string | undefined;
56
+ } | undefined;
57
+ sessions: {
58
+ id: string;
59
+ activeTurnId: string | null;
60
+ }[];
61
+ createdAt: string;
62
+ updatedAt: string;
63
+ }[];
64
+ nextCursor: string | null;
65
+ }>;
66
+ export declare function readRoutes(api: OpenAPIHono<TenantEnv>): void;
@@ -0,0 +1,127 @@
1
+ import { z } from "zod";
2
+ import { SESSION_STATUSES } from "@nylorun/core/contracts";
3
+ import { SessionManifestView, SessionUsageTotals, ModelCallsPage, ModelCallExportPage, } from "../../components.js";
4
+ import { readAccess, readStoreOf } from "../../../reads/access.js";
5
+ import { requireApplication, requirePrincipal } from "../../../tenant/auth.js";
6
+ import { fail } from "../../../tenant/http.js";
7
+ import { sandboxGrantsOf } from "../../../tenant/sandboxes.js";
8
+ import { tenantRoute } from "../define.js";
9
+ import { jsonResponse } from "../respond.js";
10
+ export const pageQuery = z.object({
11
+ limit: z.coerce.number().int().min(1).max(200).optional(),
12
+ cursor: z.string().optional(),
13
+ });
14
+ export const sessionPageQuery = pageQuery
15
+ .extend({
16
+ agentId: z.string().optional(),
17
+ status: z.enum(SESSION_STATUSES).optional(),
18
+ sandboxId: z.string().optional(),
19
+ ownerUserId: z.string().optional(),
20
+ })
21
+ .strict();
22
+ export function parseQuery(schema, query) {
23
+ const result = schema.safeParse(query);
24
+ return result.success ? result.data : fail(400, "Invalid read query", { code: "query_invalid" });
25
+ }
26
+ export async function sessionPage(ctx, scope, query) {
27
+ const { limit, cursor, ...filters } = parseQuery(sessionPageQuery, query);
28
+ if (limit === undefined)
29
+ return fail(400, "Pagination requires limit");
30
+ if (filters.ownerUserId !== undefined)
31
+ requireApplication(scope);
32
+ return readStoreOf(ctx).sessions(filters, { limit, cursor }, readAccess(scope));
33
+ }
34
+ export async function sandboxPage(ctx, scope, query, labels) {
35
+ const { limit, cursor } = parseQuery(pageQuery.extend({ label: z.string().optional() }).strict(), query);
36
+ if (limit === undefined)
37
+ return fail(400, "Pagination requires limit");
38
+ return readStoreOf(ctx).sandboxes(labels, { limit, cursor }, readAccess(scope), sandboxGrantsOf(scope));
39
+ }
40
+ export function readRoutes(api) {
41
+ const sessionId = z.object({ sessionId: z.string() });
42
+ const turnQuery = z.object({ turnId: z.string().optional() }).strict();
43
+ tenantRoute(api, { credentials: ["application", "subject"], scopes: ["agents:read", "agents:write"] }, {
44
+ method: "get",
45
+ path: "/v1/sessions/{sessionId}/manifest",
46
+ tags: ["Sessions"],
47
+ summary: "Read the session's pinned manifest",
48
+ request: { params: sessionId },
49
+ responses: {
50
+ 200: {
51
+ description: "The pinned public manifest",
52
+ content: { "application/json": { schema: SessionManifestView } },
53
+ },
54
+ },
55
+ }, async (c) => {
56
+ requirePrincipal(c.get("scope"));
57
+ return jsonResponse(200, await readStoreOf(c.env.tenant).manifest(c.req.param("sessionId"), readAccess(c.get("scope"))));
58
+ });
59
+ for (const kind of ["usage", "calls/model"]) {
60
+ const query = kind === "usage"
61
+ ? turnQuery
62
+ : turnQuery
63
+ .extend({
64
+ limit: z.coerce.number().int().min(1).max(200).default(50),
65
+ cursor: z.string().optional(),
66
+ })
67
+ .strict();
68
+ // Spend is the application's to show: no subject scope reaches it (`tenant:settings`,
69
+ // which used to, is retired in protocol 8).
70
+ tenantRoute(api, { credentials: ["application"], scopes: "never" }, {
71
+ method: "get",
72
+ path: `/v1/sessions/{sessionId}/${kind}`,
73
+ tags: ["Sessions"],
74
+ summary: kind === "usage"
75
+ ? "Read a session's recorded model usage"
76
+ : "Page a session's model calls",
77
+ request: { params: sessionId, query },
78
+ responses: {
79
+ 200: {
80
+ description: "Recorded usage, including duplicate billed calls",
81
+ content: {
82
+ "application/json": {
83
+ schema: kind === "usage" ? SessionUsageTotals : ModelCallsPage,
84
+ },
85
+ },
86
+ },
87
+ },
88
+ }, async (c) => {
89
+ requirePrincipal(c.get("scope"));
90
+ const store = readStoreOf(c.env.tenant), access = readAccess(c.get("scope")), id = c.req.param("sessionId");
91
+ if (kind === "usage") {
92
+ const q = parseQuery(turnQuery, c.req.query());
93
+ return jsonResponse(200, await store.usage(id, q.turnId, access));
94
+ }
95
+ const q = parseQuery(turnQuery
96
+ .extend({
97
+ limit: z.coerce.number().int().min(1).max(200).default(50),
98
+ cursor: z.string().optional(),
99
+ })
100
+ .strict(), c.req.query());
101
+ return jsonResponse(200, await store.modelCalls(id, q.turnId, q, access));
102
+ });
103
+ }
104
+ const exportQuery = z
105
+ .object({
106
+ after: z.string().optional(),
107
+ limit: z.coerce.number().int().min(1).max(1000).default(200),
108
+ })
109
+ .strict();
110
+ // The whole Tenant's ledger is the Management API's (protocol 8): a management key, as itself.
111
+ tenantRoute(api, { credentials: ["management"], scopes: "never" }, {
112
+ method: "get",
113
+ path: "/v1/tenant/calls/model",
114
+ tags: ["Tenant"],
115
+ summary: "Export committed model calls in safe transaction order",
116
+ request: { query: exportQuery },
117
+ responses: {
118
+ 200: {
119
+ description: "An unfiltered resumable ledger page",
120
+ content: { "application/json": { schema: ModelCallExportPage } },
121
+ },
122
+ },
123
+ }, async (c) => {
124
+ const q = parseQuery(exportQuery, c.req.query());
125
+ return jsonResponse(200, await readStoreOf(c.env.tenant).exportModel(q.after, q.limit));
126
+ });
127
+ }
@@ -1,10 +1,11 @@
1
1
  import { z } from "zod";
2
2
  import { isSandboxId, PutSandboxRequestSchema } from "@nylorun/core/contracts";
3
- import { DeleteSandboxResponse, ListSandboxEventsResponse, ListSandboxesResponse, PutSandboxRequest, SandboxView, } from "../../components.js";
3
+ import { DeleteSandboxResponse, ListSandboxEventsResponse, ListSandboxesResponse, SandboxPage, PutSandboxRequest, SandboxView, } from "../../components.js";
4
4
  import { fail } from "../../../tenant/http.js";
5
5
  import { deleteSandbox, getSandbox, listSandboxes, putSandbox, resetSandbox, sandboxEventsOf, stopSandbox, } from "../../../tenant/sandboxes.js";
6
6
  import { readJson } from "../body.js";
7
7
  import { tenantRoute } from "../define.js";
8
+ import { pageQuery, sandboxPage } from "./reads.js";
8
9
  import { jsonResponse } from "../respond.js";
9
10
  /** Reading a sandbox: any caller that may use sessions, or change sandboxes. */
10
11
  const READ = {
@@ -50,15 +51,17 @@ export function sandboxRoutes(api) {
50
51
  summary: "List sandboxes",
51
52
  description: "Every sandbox with all the labels asked for. A token caller sees only the sandboxes its grants reach.",
52
53
  request: {
53
- query: z.object({
54
+ query: pageQuery.extend({
54
55
  label: z
55
56
  .union([z.string(), z.array(z.string())])
56
57
  .optional()
57
58
  .meta({ description: "`key=value`; repeat it to require several labels" }),
58
59
  }),
59
60
  },
60
- responses: { 200: json(ListSandboxesResponse, "The sandboxes") },
61
- }, async (c) => jsonResponse(200, await listSandboxes(c.env.tenant, labelsOf(c.req.queries("label")), c.get("scope"))));
61
+ responses: { 200: json(z.union([ListSandboxesResponse, SandboxPage]), "The sandboxes; limit opts into pagination") },
62
+ }, async (c) => jsonResponse(200, c.req.query("limit") !== undefined
63
+ ? await sandboxPage(c.env.tenant, c.get("scope"), c.req.query(), labelsOf(c.req.queries("label")))
64
+ : await listSandboxes(c.env.tenant, labelsOf(c.req.queries("label")), c.get("scope"))));
62
65
  tenantRoute(api, WRITE, {
63
66
  method: "put",
64
67
  path: "/v1/sandboxes/{sandboxId}",
@@ -1,17 +1,18 @@
1
1
  import { RESPONSE_ALREADY_SENT } from "@hono/node-server/utils/response";
2
2
  import { z } from "zod";
3
- import { PutAgentRequestSchema, PutSessionRequestSchema } from "@nylorun/core/contracts";
3
+ import { PutAgentRequestSchema, PutSessionRequestSchema, SessionCommandSchema, } from "@nylorun/core/contracts";
4
4
  import { handleSessionSandboxTool, SandboxRouteError } from "../../../core/sandbox-routes.js";
5
- import { ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
6
- import { sandboxRouteDeps } from "../../../tenant/actions.js";
5
+ import { AcceptedResponse, ListAgentsResponse, ListPublicAgentsResponse, ListSessionsResponse, SessionPage, HistoryPage, SessionEvent, PutAgentRequest, PutAgentResponse, PutSessionRequest, SandboxToolOutcome, SessionCommand, SessionItemsResponse, SessionView, StreamClosedFrame, } from "../../components.js";
7
6
  import { accessOf, requireApplication } from "../../../tenant/auth.js";
7
+ import { command } from "../../../tenant/commands.js";
8
8
  import { loadSession, sessionOf } from "../../../tenant/context.js";
9
9
  import { fail } from "../../../tenant/http.js";
10
- import { sandboxGrantsOf } from "../../../tenant/sandboxes.js";
10
+ import { sandboxGrantsOf, sandboxRouteDeps } from "../../../tenant/sandboxes.js";
11
11
  import { readHistory, requestCursor, streamHolderOf, streamSessionEvents } from "../../../tenant/session-streams.js";
12
12
  import { listAgentsPublic, listDefinitions, listSessions, putDefinition, putSession, sessionView, } from "../../../tenant/sessions.js";
13
13
  import { readJson } from "../body.js";
14
14
  import { tenantRoute } from "../define.js";
15
+ import { pageQuery, sessionPage, sessionPageQuery, parseQuery } from "./reads.js";
15
16
  import { jsonResponse } from "../respond.js";
16
17
  const OWN_SESSIONS = {
17
18
  credentials: ["application", "subject", "token"],
@@ -45,6 +46,18 @@ async function sessionBelow(ctx, incoming, id, access) {
45
46
  return requestCursor(incoming, urlOf(incoming));
46
47
  }
47
48
  export function sessionRoutes(api) {
49
+ tenantRoute(api, OWN_SESSIONS, {
50
+ method: "post",
51
+ path: "/v1/sessions/{sessionId}/commands",
52
+ tags: ["Sessions"],
53
+ summary: "Send a command to a session",
54
+ 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
+ responses: {
57
+ 200: json(AcceptedResponse, "The command, accepted"),
58
+ 409: { description: "A turn is running, or the session does not wait on this" },
59
+ },
60
+ }, async (c) => jsonResponse(200, await command(c.env.tenant, c.req.param("sessionId"), SessionCommandSchema.parse(await readJson(c.req.raw)), c.get("scope"))));
48
61
  tenantRoute(api, {
49
62
  credentials: ["application", "subject", "token"],
50
63
  scopes: ["agents:read", "agents:write"],
@@ -80,10 +93,12 @@ export function sessionRoutes(api) {
80
93
  tags: ["Sessions"],
81
94
  summary: "List sessions",
82
95
  description: "Acting for a person, only theirs.",
83
- request: { query: z.object({ agentId: z.string().optional() }) },
84
- responses: { 200: json(ListSessionsResponse, "The sessions") },
96
+ request: { query: sessionPageQuery },
97
+ responses: { 200: json(z.union([ListSessionsResponse, SessionPage]), "The sessions; limit opts into pagination") },
85
98
  }, async (c) => {
86
99
  const scope = c.get("scope");
100
+ if (c.req.query("limit") !== undefined)
101
+ return jsonResponse(200, await sessionPage(c.env.tenant, scope, c.req.query()));
87
102
  return jsonResponse(200, await listSessions(c.env.tenant, c.req.query("agentId") ?? null, accessOf(scope)));
88
103
  });
89
104
  tenantRoute(api, OWN_SESSIONS, {
@@ -129,19 +144,21 @@ export function sessionRoutes(api) {
129
144
  request: {
130
145
  params: sessionId,
131
146
  query: resume.extend({
147
+ limit: pageQuery.shape.limit,
132
148
  agent: z
133
149
  .string()
134
150
  .optional()
135
151
  .meta({ description: "Only the events of this agent (the root agent, or one it uses as a tool)" }),
136
152
  }),
137
153
  },
138
- responses: { 200: json(SessionItemsResponse, "Events up to now, and the cursor to continue") },
154
+ responses: { 200: json(z.union([SessionItemsResponse, HistoryPage]), "Events up to now; limit returns a bounded page and tail") },
139
155
  }, async (c) => {
140
156
  const scope = c.get("scope");
141
157
  const ctx = c.env.tenant;
142
158
  const id = c.req.param("sessionId");
143
159
  const cursor = await sessionBelow(ctx, c.env.incoming, id, accessOf(scope));
144
- return jsonResponse(200, await readHistory(ctx, id, cursor, c.req.query("agent")));
160
+ const { limit } = parseQuery(pageQuery.pick({ limit: true }), { limit: c.req.query("limit") });
161
+ return jsonResponse(200, await readHistory(ctx, id, cursor, c.req.query("agent"), limit));
145
162
  });
146
163
  tenantRoute(api, OWN_SESSIONS, {
147
164
  method: "get",
@@ -16,11 +16,8 @@ import { readJson } from "../body.js";
16
16
  import { pathSegments, tenantRoute } from "../define.js";
17
17
  import { jsonResponse } from "../respond.js";
18
18
  import { rearmPodTimers } from "../../../tenant/sandboxes.js";
19
- const settings = (scopes) => ({
20
- credentials: scopes === "never" ? ["application"] : ["application", "subject"],
21
- scopes,
22
- });
23
- const SETTINGS = settings(["tenant:settings"]);
19
+ /** Every Management API route (`/v1/tenant/*`): a management key, acting as itself (protocol 8). */
20
+ const MANAGEMENT = { credentials: ["management"], scopes: "never" };
24
21
  const json = (schema, description) => ({
25
22
  description,
26
23
  content: { "application/json": { schema } },
@@ -79,7 +76,7 @@ async function clusterOf(ctx) {
79
76
  }
80
77
  }
81
78
  export function tenantRoutes(api) {
82
- tenantRoute(api, SETTINGS, {
79
+ tenantRoute(api, MANAGEMENT, {
83
80
  method: "get",
84
81
  path: "/v1/tenant",
85
82
  tags: ["Tenant"],
@@ -103,7 +100,7 @@ export function tenantRoutes(api) {
103
100
  harness: harnessStatusOf(ctx),
104
101
  }));
105
102
  });
106
- tenantRoute(api, settings("never"), {
103
+ tenantRoute(api, MANAGEMENT, {
107
104
  method: "post",
108
105
  path: "/v1/tenant/reset",
109
106
  tags: ["Tenant"],
@@ -134,7 +131,7 @@ export function tenantRoutes(api) {
134
131
  ctx.closing = false;
135
132
  return jsonResponse(200, { ok: true });
136
133
  });
137
- tenantRoute(api, settings("never"), {
134
+ tenantRoute(api, MANAGEMENT, {
138
135
  method: "put",
139
136
  path: "/v1/tenant/config/seed",
140
137
  tags: ["Tenant"],
@@ -147,7 +144,7 @@ export function tenantRoutes(api) {
147
144
  const request = SeedTenantConfigRequestSchema.parse(await readJson(c.req.raw));
148
145
  return jsonResponse(200, await seedTenantConfig({ store: ctx.store, vault: ctx.vault, keys: ctx.keys }, request));
149
146
  });
150
- tenantRoute(api, settings(["tenant:settings", "agents:write"]), {
147
+ tenantRoute(api, MANAGEMENT, {
151
148
  method: "get",
152
149
  path: "/v1/tenant/models",
153
150
  tags: ["Tenant"],
@@ -157,14 +154,14 @@ export function tenantRoutes(api) {
157
154
  c.env.tenant;
158
155
  return jsonResponse(200, hostModelCatalog());
159
156
  });
160
- tenantRoute(api, SETTINGS, {
157
+ tenantRoute(api, MANAGEMENT, {
161
158
  method: "get",
162
159
  path: "/v1/tenant/sandbox",
163
160
  tags: ["Tenant"],
164
161
  summary: "Get the Tenant's sandbox configuration",
165
162
  responses: { 200: json(TenantSandboxView, "The backend in use and the configuration in force") },
166
163
  }, async (c) => jsonResponse(200, await sandboxView(c.env.tenant)));
167
- tenantRoute(api, SETTINGS, {
164
+ tenantRoute(api, MANAGEMENT, {
168
165
  method: "put",
169
166
  path: "/v1/tenant/sandbox",
170
167
  tags: ["Tenant"],
@@ -183,7 +180,7 @@ export function tenantRoutes(api) {
183
180
  await rearmPodTimers(ctx);
184
181
  return jsonResponse(200, await sandboxView(ctx));
185
182
  });
186
- tenantRoute(api, SETTINGS, {
183
+ tenantRoute(api, MANAGEMENT, {
187
184
  method: "get",
188
185
  path: "/v1/tenant/usage",
189
186
  tags: ["Tenant"],
@@ -212,7 +209,7 @@ export function tenantRoutes(api) {
212
209
  ...totals,
213
210
  });
214
211
  });
215
- tenantRoute(api, SETTINGS, {
212
+ tenantRoute(api, MANAGEMENT, {
216
213
  method: "get",
217
214
  path: "/v1/tenant/budgets",
218
215
  tags: ["Tenant"],
@@ -222,7 +219,7 @@ export function tenantRoutes(api) {
222
219
  const rows = await c.env.tenant.store.tx((t) => t.listModelBudgets());
223
220
  return jsonResponse(200, { budgets: rows.map(budgetView) });
224
221
  });
225
- tenantRoute(api, SETTINGS, {
222
+ tenantRoute(api, MANAGEMENT, {
226
223
  method: "put",
227
224
  path: "/v1/tenant/budgets",
228
225
  tags: ["Tenant"],
@@ -239,21 +236,21 @@ export function tenantRoutes(api) {
239
236
  });
240
237
  return jsonResponse(200, { budgets: rows.map(budgetView) });
241
238
  });
242
- tenantRoute(api, settings(["tenant:settings", "agents:write"]), {
239
+ tenantRoute(api, MANAGEMENT, {
243
240
  method: "get",
244
241
  path: "/v1/tenant/providers",
245
242
  tags: ["Tenant"],
246
243
  summary: "List the model providers the Tenant has credentials for",
247
244
  responses: { 200: json(ListProvidersResponse, "The providers") },
248
245
  }, async (c) => jsonResponse(200, await (c.env.tenant).vault.listHostProviders()));
249
- tenantRoute(api, SETTINGS, {
246
+ tenantRoute(api, MANAGEMENT, {
250
247
  method: "get",
251
248
  path: "/v1/tenant/model",
252
249
  tags: ["Tenant"],
253
250
  summary: "Get the Tenant's model",
254
251
  responses: { 200: json(HostModelView, "The model its sessions call") },
255
252
  }, async (c) => jsonResponse(200, await (c.env.tenant).vault.getHostModel()));
256
- tenantRoute(api, SETTINGS, {
253
+ tenantRoute(api, MANAGEMENT, {
257
254
  method: "put",
258
255
  path: "/v1/tenant/model",
259
256
  tags: ["Tenant"],
@@ -264,7 +261,7 @@ export function tenantRoutes(api) {
264
261
  const ctx = c.env.tenant;
265
262
  return jsonResponse(200, await ctx.keys.putHostModel(PutHostModelRequestSchema.parse(await readJson(c.req.raw))));
266
263
  });
267
- tenantRoute(api, SETTINGS, {
264
+ tenantRoute(api, MANAGEMENT, {
268
265
  method: "put",
269
266
  path: "/v1/tenant/model/selection",
270
267
  tags: ["Tenant"],
@@ -1,14 +1,15 @@
1
1
  /**
2
- * Vaults and their credentials (`/v1/vaults/**`): secrets a session's tools use. Only an
3
- * application key acting for no one (an operator key, Studio) reaches these routes (protocol 7):
4
- * a request acting for a subject, or a trusted issuer's token, is `403 scope_required`. The
5
- * installation's own vaults (`scope: "installation"`) attach to any session; a vault of one
6
- * person (`ownerUserId`) attaches only to that person's sessions. A person's own credentials
7
- * come from the operator's credential resolver (`vault/sources.ts`), not from these routes.
2
+ * Vaults and their credentials (`/v1/tenant/vaults/**`, the Management API, protocol 8): secrets
3
+ * a session's tools use. Only a management key (or Studio's key, acting as itself) reaches these
4
+ * routes; an application key is `403 key_role_mismatch`, and nothing acts for a subject here. The
5
+ * installation's own vaults (`scope: "installation"`) attach to any session (`vaultIds`); a vault
6
+ * of one person (`ownerUserId`) attaches only to that person's sessions. A person's own
7
+ * credentials come from the operator's credential resolver (`vault/sources.ts`), not from these
8
+ * routes.
8
9
  *
9
- * MCP OAuth connect (F9 C2): an application key starts one into an installation vault
10
- * (`/oauth/start`); the authorization server sends the browser back to the anonymous, unversioned
11
- * `GET /v1/oauth/callback`. Both only route: the gateway's keys module does the OAuth (F9-D14).
10
+ * MCP OAuth connect: a management key starts one into an installation vault (`/oauth/start`);
11
+ * the authorization server sends the browser back to the anonymous, unversioned
12
+ * `GET /v1/oauth/callback`. Both only route: the gateway's keys module does the OAuth.
12
13
  */
13
14
  import type { OpenAPIHono } from "@hono/zod-openapi";
14
15
  import type { TenantEnv } from "../app.js";