@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,272 @@
1
+ import { z } from "zod";
2
+ /** Drizzle read projections on a separate, read-only Tenant pool. */
3
+ import { and, asc, desc, eq, gt, inArray, isNull, lt, ne, or, sql, } from "drizzle-orm";
4
+ import { readCursor } from "../../reads/cursor.js";
5
+ import { fail } from "../../tenant/http.js";
6
+ import { sandboxWorkspaceKey } from "../../sandbox/records.js";
7
+ import { database, driverError } from "./db.js";
8
+ import { createPostgresReadClient } from "./connect.js";
9
+ import { modelUsage, sandboxResources, sandboxes, sessions, sessionEvents, } from "./schema.js";
10
+ const sessionFields = {
11
+ id: sessions.id,
12
+ agentId: sessions.agentId,
13
+ ownerUserId: sessions.ownerUserId,
14
+ status: sessions.status,
15
+ activeTurnId: sql `${sessions.body}->>'activeTurnId'`,
16
+ lastTurnId: sql `${sessions.body}->>'lastTurnId'`,
17
+ sandboxId: sessions.sandboxId,
18
+ createdAt: sessions.createdAt,
19
+ };
20
+ function accessWhere(access) {
21
+ return and(access.owner === undefined ? undefined : eq(sessions.ownerUserId, access.owner), access.agents === undefined ? undefined : inArray(sessions.agentId, [...access.agents]));
22
+ }
23
+ function publicCall(row) {
24
+ return {
25
+ id: row.id,
26
+ sessionId: row.sessionId,
27
+ turnId: row.turnId,
28
+ agentId: row.agentId,
29
+ provider: row.provider,
30
+ model: row.model,
31
+ at: row.createdAt,
32
+ outcome: "completed",
33
+ usage: {
34
+ inputTokens: row.inputTokens,
35
+ outputTokens: row.outputTokens,
36
+ totalTokens: row.totalTokens,
37
+ cachedTokens: row.cachedTokens,
38
+ cacheWriteTokens: row.cacheWriteTokens,
39
+ reasoningTokens: row.reasoningTokens,
40
+ costUsd: row.costUsd,
41
+ duplicate: row.duplicate,
42
+ tokensReported: row.tokensReported,
43
+ costKnown: row.costKnown,
44
+ },
45
+ };
46
+ }
47
+ function keyOf(key, length, timestamp = false, nullable = false) {
48
+ if (key &&
49
+ (key.length !== length ||
50
+ key.at(-1) === "" ||
51
+ key.some((v, i) => v === null && !(nullable && i === 0))))
52
+ fail(400, "Invalid cursor key", { code: "cursor_invalid" });
53
+ if (key &&
54
+ timestamp &&
55
+ key[0] !== null &&
56
+ !z.iso
57
+ .datetime({ offset: true })
58
+ .safeParse(key[0].replace(" ", "T").replace(/([+-]\d{2})$/, "$1:00")).success)
59
+ fail(400, "Invalid cursor timestamp", { code: "cursor_invalid" });
60
+ return key;
61
+ }
62
+ export function createPostgresReadStore(source, tenantId) {
63
+ const pool = createPostgresReadClient(source);
64
+ const db = database(pool);
65
+ const latestEvent = db
66
+ .select({ at: sessionEvents.committedAt })
67
+ .from(sessionEvents)
68
+ .where(eq(sessionEvents.sessionId, sessions.id))
69
+ .orderBy(desc(sessionEvents.seq))
70
+ .limit(1);
71
+ const fields = { ...sessionFields, lastEventAt: sql `(${latestEvent})` };
72
+ const read = (fn) => db.transaction(fn, { accessMode: "read only" }).catch((error) => {
73
+ const cause = driverError(error);
74
+ if (cause && typeof cause === "object" && "code" in cause && cause.code === "57014")
75
+ fail(503, "Read timed out", { code: "read_timeout" });
76
+ throw cause;
77
+ });
78
+ async function requireSession(tx, id, access) {
79
+ const [row] = await tx
80
+ .select({ id: sessions.id })
81
+ .from(sessions)
82
+ .where(and(eq(sessions.id, id), accessWhere(access)))
83
+ .limit(1);
84
+ if (!row)
85
+ return fail(404, "Session not found");
86
+ return row.id;
87
+ }
88
+ return {
89
+ close: () => pool.end({ timeout: 2 }),
90
+ sessions: (filters, page, access) => read(async (tx) => {
91
+ const cursor = readCursor(tenantId, "sessions", filters);
92
+ const key = keyOf(cursor.decode(page.cursor), 2, true, true);
93
+ const position = !key
94
+ ? undefined
95
+ : key[0] === null
96
+ ? and(isNull(sessions.createdAt), lt(sessions.id, key[1]))
97
+ : or(lt(sessions.createdAt, key[0]), and(eq(sessions.createdAt, key[0]), lt(sessions.id, key[1])), isNull(sessions.createdAt));
98
+ const rows = await tx
99
+ .select(fields)
100
+ .from(sessions)
101
+ .where(and(accessWhere(access), position, filters.agentId === undefined ? undefined : eq(sessions.agentId, filters.agentId), filters.status === undefined ? undefined : eq(sessions.status, filters.status), filters.sandboxId === undefined
102
+ ? undefined
103
+ : eq(sessions.sandboxId, filters.sandboxId), filters.ownerUserId === undefined
104
+ ? undefined
105
+ : eq(sessions.ownerUserId, filters.ownerUserId)))
106
+ .orderBy(sql `${sessions.createdAt} desc nulls last`, desc(sessions.id))
107
+ .limit(page.limit + 1);
108
+ const items = rows.slice(0, page.limit).map((r) => ({
109
+ ...r,
110
+ createdAt: r.createdAt ? new Date(r.createdAt).toISOString() : null,
111
+ lastEventAt: r.lastEventAt ? new Date(r.lastEventAt).toISOString() : null,
112
+ }));
113
+ const last = rows.slice(0, page.limit).at(-1);
114
+ return {
115
+ sessions: items,
116
+ nextCursor: rows.length > page.limit && last ? cursor.encode([last.createdAt, last.id]) : null,
117
+ };
118
+ }),
119
+ manifest: (id, access) => read(async (tx) => {
120
+ const [row] = await tx
121
+ .select({
122
+ sessionId: sessions.id,
123
+ agentId: sessions.agentId,
124
+ manifestHash: sql `${sessions.body}->>'manifestHash'`,
125
+ implementationVersion: sql `${sessions.body}->>'implementationVersion'`,
126
+ manifest: sql `${sessions.body}->'manifest'`,
127
+ })
128
+ .from(sessions)
129
+ .where(and(eq(sessions.id, id), accessWhere(access)))
130
+ .limit(1);
131
+ if (!row)
132
+ return fail(404, "Session not found");
133
+ return row;
134
+ }),
135
+ usage: (id, turnId, access) => read(async (tx) => {
136
+ await requireSession(tx, id, access);
137
+ const sum = (column) => sql `coalesce(sum(${column}), 0)`.mapWith(Number);
138
+ const sums = {
139
+ inputTokens: sum(modelUsage.inputTokens),
140
+ outputTokens: sum(modelUsage.outputTokens),
141
+ totalTokens: sum(modelUsage.totalTokens),
142
+ cachedTokens: sum(modelUsage.cachedTokens),
143
+ cacheWriteTokens: sum(modelUsage.cacheWriteTokens),
144
+ reasoningTokens: sum(modelUsage.reasoningTokens),
145
+ costUsd: sum(modelUsage.costUsd),
146
+ };
147
+ const [totals] = await tx
148
+ .select({
149
+ ...sums,
150
+ calls: sql `count(*)`.mapWith(Number),
151
+ duplicates: sql `count(*) filter (where ${modelUsage.duplicate})`.mapWith(Number),
152
+ unpricedCalls: sql `count(*) filter (where ${modelUsage.costKnown} = false)`.mapWith(Number),
153
+ unreportedCalls: sql `count(*) filter (where ${modelUsage.tokensReported} = false)`.mapWith(Number),
154
+ unknownQualityCalls: sql `count(*) filter (where ${modelUsage.tokensReported} is null or ${modelUsage.costKnown} is null)`.mapWith(Number),
155
+ })
156
+ .from(modelUsage)
157
+ .where(and(eq(modelUsage.sessionId, id), turnId === undefined ? undefined : eq(modelUsage.turnId, turnId)));
158
+ return {
159
+ ...totals,
160
+ sessionId: id,
161
+ turnId: turnId ?? null,
162
+ asOf: new Date().toISOString(),
163
+ };
164
+ }),
165
+ modelCalls: (id, turnId, page, access) => read(async (tx) => {
166
+ await requireSession(tx, id, access);
167
+ const cursor = readCursor(tenantId, "model-calls", { sessionId: id, turnId });
168
+ const key = keyOf(cursor.decode(page.cursor), 2, true);
169
+ const rows = await tx
170
+ .select()
171
+ .from(modelUsage)
172
+ .where(and(eq(modelUsage.sessionId, id), turnId === undefined ? undefined : eq(modelUsage.turnId, turnId), key
173
+ ? sql `(${modelUsage.createdAt}, ${modelUsage.id}) > (${key[0]}, ${key[1]})`
174
+ : undefined))
175
+ .orderBy(asc(modelUsage.createdAt), asc(modelUsage.id))
176
+ .limit(page.limit + 1);
177
+ const items = rows.slice(0, page.limit);
178
+ const last = items.at(-1);
179
+ return {
180
+ calls: items.map(publicCall),
181
+ nextCursor: rows.length > page.limit && last ? cursor.encode([last.createdAt, last.id]) : null,
182
+ asOf: new Date().toISOString(),
183
+ };
184
+ }),
185
+ exportModel: (after, limit) => read(async (tx) => {
186
+ const cursor = readCursor(tenantId, "model-export", {});
187
+ const key = keyOf(cursor.decode(after), 2);
188
+ if (key &&
189
+ (key[0] === null || !/^\d+$/.test(key[0]) || BigInt(key[0]) > 18446744073709551615n))
190
+ fail(400, "Invalid export cursor", { code: "cursor_invalid" });
191
+ const rows = await tx
192
+ .select()
193
+ .from(modelUsage)
194
+ .where(and(sql `${modelUsage.txid} < pg_snapshot_xmin(pg_current_snapshot())`, key
195
+ ? sql `(${modelUsage.txid}, ${modelUsage.id}) > (${key[0]}::xid8, ${key[1]})`
196
+ : undefined))
197
+ .orderBy(asc(modelUsage.txid), asc(modelUsage.id))
198
+ .limit(limit + 1);
199
+ const items = rows.slice(0, limit);
200
+ const last = items.at(-1);
201
+ return {
202
+ calls: items.map(publicCall),
203
+ next: last ? cursor.encode([last.txid, last.id]) : (after ?? null),
204
+ caughtUp: rows.length <= limit,
205
+ asOf: new Date().toISOString(),
206
+ };
207
+ }),
208
+ sandboxes: (labels, page, access, grants) => read(async (tx) => {
209
+ const cursor = readCursor(tenantId, "sandboxes", labels);
210
+ const key = keyOf(cursor.decode(page.cursor), 1);
211
+ const allowed = grants === undefined
212
+ ? undefined
213
+ : or(...grants.map((grant) => grant.endsWith("/*")
214
+ ? sql `starts_with(${sandboxResources.id}, ${grant.slice(0, -1)})`
215
+ : eq(sandboxResources.id, grant)));
216
+ const rows = await tx
217
+ .select()
218
+ .from(sandboxResources)
219
+ .where(and(grants?.length === 0 ? sql `false` : allowed, key ? gt(sandboxResources.id, key[0]) : undefined, or(isNull(sandboxResources.desired), ne(sandboxResources.desired, "deleted")), ...Object.entries(labels).map(([k, v]) => sql `${sandboxResources.labels}::jsonb->>${k} = ${v}`)))
220
+ .orderBy(asc(sandboxResources.id))
221
+ .limit(page.limit + 1);
222
+ const items = [];
223
+ for (const row of rows.slice(0, page.limit)) {
224
+ const [related] = await tx
225
+ .select({
226
+ items: sql `coalesce(json_agg(json_build_object('id', ${sessions.id}, 'activeTurnId', ${sessions.body}->>'activeTurnId') order by ${sessions.id}), '[]'::json)`,
227
+ })
228
+ .from(sessions)
229
+ .where(and(eq(sessions.sandboxId, row.id), accessWhere(access)));
230
+ const [compute] = await tx
231
+ .select({ body: sandboxes.body })
232
+ .from(sandboxes)
233
+ .where(eq(sandboxes.id, sandboxWorkspaceKey(tenantId, row.id)))
234
+ .limit(1);
235
+ const isPod = row.kind === "pod";
236
+ items.push({
237
+ id: row.id,
238
+ kind: row.kind,
239
+ labels: row.labels,
240
+ spec: row.spec,
241
+ state: isPod
242
+ ? row.observed === "running"
243
+ ? "running"
244
+ : row.observed === "creating"
245
+ ? "creating"
246
+ : "stopped"
247
+ : (compute?.body?.state ?? "ready"),
248
+ ...(isPod
249
+ ? {
250
+ pod: {
251
+ desired: row.desired,
252
+ observed: row.observed,
253
+ volumeGeneration: row.volumeGen,
254
+ hostEpoch: row.hostEpoch,
255
+ ...(row.expiresAt ? { expiresAt: new Date(row.expiresAt).toISOString() } : {}),
256
+ ...(row.reason ? { reason: row.reason } : {}),
257
+ },
258
+ }
259
+ : {}),
260
+ sessions: related.items,
261
+ createdAt: row.createdAt,
262
+ updatedAt: row.updatedAt,
263
+ });
264
+ }
265
+ const last = items.at(-1);
266
+ return {
267
+ sandboxes: items,
268
+ nextCursor: rows.length > page.limit && last ? cursor.encode([last.id]) : null,
269
+ };
270
+ }),
271
+ };
272
+ }