@opengeni/db 0.2.2 → 0.3.0

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.
package/dist/index.js CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  sessionEvents,
27
27
  sessionGoals,
28
28
  sessionHistoryItems,
29
+ sessionMcpServers,
29
30
  sessionRecordings,
30
31
  sessionTurns,
31
32
  sessions,
@@ -38,7 +39,7 @@ import {
38
39
  workspaceMemberships,
39
40
  workspacePacks,
40
41
  workspaces
41
- } from "./chunk-PSX56ZTL.js";
42
+ } from "./chunk-NZA6YVN7.js";
42
43
  import {
43
44
  migrate,
44
45
  runMigrations
@@ -144,12 +145,64 @@ function sanitizeEventPayload(payload) {
144
145
  }
145
146
  if (payload && typeof payload === "object") {
146
147
  const entries = Object.entries(payload).map(
147
- ([key, value]) => [sanitizeEventString(key), sanitizeEventPayload(value)]
148
+ ([key, value]) => [sanitizeEventString(key), sanitizeSensitiveEventField(key, value)]
148
149
  );
149
150
  return Object.fromEntries(entries);
150
151
  }
151
152
  return payload;
152
153
  }
154
+ function sanitizeSensitiveEventField(key, value) {
155
+ if (key === "mcpServers") {
156
+ return sanitizeSessionMcpServerList(value);
157
+ }
158
+ if (key === "mcpCredentialUpdates") {
159
+ return sanitizeMcpCredentialUpdateList(value);
160
+ }
161
+ return sanitizeEventPayload(value);
162
+ }
163
+ function sanitizeSessionMcpServerList(value) {
164
+ if (!Array.isArray(value)) {
165
+ return sanitizeEventPayload(value);
166
+ }
167
+ return value.map((item) => {
168
+ if (!isPlainObject(item)) {
169
+ return sanitizeEventPayload(item);
170
+ }
171
+ const { headers, headersEncrypted, ...rest } = item;
172
+ const cleaned = sanitizeEventPayload(rest);
173
+ const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
174
+ if (headerNames) {
175
+ cleaned.headerNames = headerNames;
176
+ }
177
+ return cleaned;
178
+ });
179
+ }
180
+ function sanitizeMcpCredentialUpdateList(value) {
181
+ if (!Array.isArray(value)) {
182
+ return sanitizeEventPayload(value);
183
+ }
184
+ return value.map((item) => {
185
+ if (!isPlainObject(item)) {
186
+ return sanitizeEventPayload(item);
187
+ }
188
+ const { headers, headersEncrypted, ...rest } = item;
189
+ const cleaned = sanitizeEventPayload(rest);
190
+ const headerNames = safeHeaderNames(headers) ?? safeHeaderNames(headersEncrypted);
191
+ if (headerNames) {
192
+ cleaned.headerNames = headerNames;
193
+ }
194
+ return cleaned;
195
+ });
196
+ }
197
+ function safeHeaderNames(value) {
198
+ if (!isPlainObject(value)) {
199
+ return null;
200
+ }
201
+ return Object.keys(value).map(sanitizeEventString).sort();
202
+ }
203
+ function isPlainObject(value) {
204
+ return Boolean(value && typeof value === "object" && !Array.isArray(value));
205
+ }
153
206
 
154
207
  // src/index.ts
155
208
  import { sql as sql2 } from "drizzle-orm";
@@ -388,6 +441,7 @@ var allWorkspacePermissions = [
388
441
  "api_keys:manage",
389
442
  "environments:manage",
390
443
  "environments:use",
444
+ "mcp_servers:attach",
391
445
  "goals:manage",
392
446
  "enrollments:read",
393
447
  "enrollments:manage"
@@ -2093,6 +2147,111 @@ function mapWorkspaceEnvironmentVariableMetadata(row) {
2093
2147
  updatedAt: row.updatedAt.toISOString()
2094
2148
  };
2095
2149
  }
2150
+ function mapSessionMcpServerMetadata(row) {
2151
+ return {
2152
+ id: row.serverId,
2153
+ name: row.name ?? null,
2154
+ url: row.url,
2155
+ headerNames: Object.keys(row.headersEncrypted ?? {}).sort(),
2156
+ credentialVersion: Number(row.credentialVersion)
2157
+ };
2158
+ }
2159
+ async function sessionMcpServerMetadataForSessions(db, workspaceId, sessionIds) {
2160
+ const grouped = /* @__PURE__ */ new Map();
2161
+ if (sessionIds.length === 0) {
2162
+ return grouped;
2163
+ }
2164
+ const rows = await db.select().from(sessionMcpServers).where(and(
2165
+ eq(sessionMcpServers.workspaceId, workspaceId),
2166
+ inArray(sessionMcpServers.sessionId, sessionIds)
2167
+ )).orderBy(asc(sessionMcpServers.createdAt), asc(sessionMcpServers.serverId));
2168
+ for (const row of rows) {
2169
+ const list = grouped.get(row.sessionId) ?? [];
2170
+ list.push(mapSessionMcpServerMetadata(row));
2171
+ grouped.set(row.sessionId, list);
2172
+ }
2173
+ return grouped;
2174
+ }
2175
+ async function insertSessionMcpServers(db, input) {
2176
+ if (input.servers.length === 0) {
2177
+ return [];
2178
+ }
2179
+ const rows = await db.insert(sessionMcpServers).values(input.servers.map((server) => ({
2180
+ accountId: input.accountId,
2181
+ workspaceId: input.workspaceId,
2182
+ sessionId: input.sessionId,
2183
+ serverId: server.id,
2184
+ name: server.name ?? null,
2185
+ url: server.url,
2186
+ allowedTools: server.allowedTools ?? null,
2187
+ timeoutMs: server.timeoutMs ?? null,
2188
+ cacheToolsList: server.cacheToolsList ?? false,
2189
+ headersEncrypted: server.headersEncrypted ?? {}
2190
+ }))).returning();
2191
+ return rows.map(mapSessionMcpServerMetadata);
2192
+ }
2193
+ async function createSessionMcpServers(db, input) {
2194
+ return await withRlsContext(
2195
+ db,
2196
+ { accountId: input.accountId, workspaceId: input.workspaceId },
2197
+ async (scopedDb) => await insertSessionMcpServers(scopedDb, input)
2198
+ );
2199
+ }
2200
+ async function listSessionMcpServerMetadata(db, workspaceId, sessionId) {
2201
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2202
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [sessionId]);
2203
+ return grouped.get(sessionId) ?? [];
2204
+ });
2205
+ }
2206
+ async function updateSessionMcpServerCredentials(db, input) {
2207
+ return await withWorkspaceRls(db, input.workspaceId, async (scopedDb) => await scopedDb.transaction(
2208
+ async (tx) => await updateSessionMcpServerCredentialsInTransaction(tx, input)
2209
+ ));
2210
+ }
2211
+ async function updateSessionMcpServerCredentialsInTransaction(tx, input) {
2212
+ const servers = [];
2213
+ const missingIds = [];
2214
+ for (const update of input.updates) {
2215
+ const [row] = await tx.update(sessionMcpServers).set({
2216
+ headersEncrypted: update.headersEncrypted,
2217
+ credentialVersion: sql`${sessionMcpServers.credentialVersion} + 1`,
2218
+ updatedAt: /* @__PURE__ */ new Date()
2219
+ }).where(and(
2220
+ eq(sessionMcpServers.workspaceId, input.workspaceId),
2221
+ eq(sessionMcpServers.sessionId, input.sessionId),
2222
+ eq(sessionMcpServers.serverId, update.id)
2223
+ )).returning();
2224
+ if (!row) {
2225
+ missingIds.push(update.id);
2226
+ } else {
2227
+ servers.push(mapSessionMcpServerMetadata(row));
2228
+ }
2229
+ }
2230
+ return { servers, missingIds };
2231
+ }
2232
+ async function listSessionMcpServersForRun(db, workspaceId, sessionId, encryptionKey) {
2233
+ return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2234
+ const rows = await scopedDb.select().from(sessionMcpServers).where(and(
2235
+ eq(sessionMcpServers.workspaceId, workspaceId),
2236
+ eq(sessionMcpServers.sessionId, sessionId)
2237
+ )).orderBy(asc(sessionMcpServers.createdAt), asc(sessionMcpServers.serverId));
2238
+ return rows.map((row) => {
2239
+ let headers;
2240
+ try {
2241
+ headers = Object.fromEntries(Object.entries(row.headersEncrypted ?? {}).map(([name, stored]) => [name, decryptEnvironmentValue(encryptionKey, stored)]));
2242
+ } catch {
2243
+ throw new Error("session MCP server credential decryption failed");
2244
+ }
2245
+ return {
2246
+ ...mapSessionMcpServerMetadata(row),
2247
+ ...row.allowedTools ? { allowedTools: row.allowedTools } : {},
2248
+ ...row.timeoutMs ? { timeoutMs: row.timeoutMs } : {},
2249
+ ...row.cacheToolsList ? { cacheToolsList: row.cacheToolsList } : {},
2250
+ headers
2251
+ };
2252
+ });
2253
+ });
2254
+ }
2096
2255
  async function createSession(db, input) {
2097
2256
  const id = crypto.randomUUID();
2098
2257
  return await withRlsContext(db, { accountId: input.accountId, workspaceId: input.workspaceId }, async (scopedDb) => {
@@ -2117,7 +2276,13 @@ async function createSession(db, input) {
2117
2276
  if (!row) {
2118
2277
  throw new Error("Failed to create session");
2119
2278
  }
2120
- return mapSession(row);
2279
+ const mcpServers = await insertSessionMcpServers(scopedDb, {
2280
+ accountId: input.accountId,
2281
+ workspaceId: input.workspaceId,
2282
+ sessionId: row.id,
2283
+ servers: input.mcpServers ?? []
2284
+ });
2285
+ return mapSession(row, mcpServers);
2121
2286
  });
2122
2287
  }
2123
2288
  async function createSessionWithIdempotencyKey(db, input) {
@@ -2145,7 +2310,13 @@ async function createSessionWithIdempotencyKey(db, input) {
2145
2310
  where: sql`${sessions.createIdempotencyKey} is not null`
2146
2311
  }).returning();
2147
2312
  if (inserted) {
2148
- return { session: mapSession(inserted), created: true };
2313
+ const mcpServers = await insertSessionMcpServers(scopedDb, {
2314
+ accountId: input.accountId,
2315
+ workspaceId: input.workspaceId,
2316
+ sessionId: inserted.id,
2317
+ servers: input.mcpServers ?? []
2318
+ });
2319
+ return { session: mapSession(inserted, mcpServers), created: true };
2149
2320
  }
2150
2321
  const [existing] = await scopedDb.select().from(sessions).where(and(
2151
2322
  eq(sessions.workspaceId, input.workspaceId),
@@ -2154,7 +2325,8 @@ async function createSessionWithIdempotencyKey(db, input) {
2154
2325
  if (!existing) {
2155
2326
  throw new Error("Failed to create session under idempotency key");
2156
2327
  }
2157
- return { session: mapSession(existing), created: false };
2328
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, input.workspaceId, [existing.id]);
2329
+ return { session: mapSession(existing, grouped.get(existing.id) ?? []), created: false };
2158
2330
  });
2159
2331
  }
2160
2332
  async function getSessionByCreateIdempotencyKey(db, workspaceId, createIdempotencyKey) {
@@ -2163,13 +2335,17 @@ async function getSessionByCreateIdempotencyKey(db, workspaceId, createIdempoten
2163
2335
  eq(sessions.workspaceId, workspaceId),
2164
2336
  eq(sessions.createIdempotencyKey, createIdempotencyKey)
2165
2337
  )).limit(1);
2166
- return row ? mapSession(row) : null;
2338
+ if (!row) return null;
2339
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [row.id]);
2340
+ return mapSession(row, grouped.get(row.id) ?? []);
2167
2341
  });
2168
2342
  }
2169
2343
  async function getSession(db, workspaceId, sessionId) {
2170
2344
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2171
2345
  const [row] = await scopedDb.select().from(sessions).where(and(eq(sessions.workspaceId, workspaceId), eq(sessions.id, sessionId))).limit(1);
2172
- return row ? mapSession(row) : null;
2346
+ if (!row) return null;
2347
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, [row.id]);
2348
+ return mapSession(row, grouped.get(row.id) ?? []);
2173
2349
  });
2174
2350
  }
2175
2351
  async function getAnySessionInGroup(db, workspaceId, sandboxGroupId) {
@@ -2187,7 +2363,8 @@ async function listDistinctEnvironmentIdsInGroup(db, workspaceId, sandboxGroupId
2187
2363
  async function listSessions(db, workspaceId, limit = 50) {
2188
2364
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2189
2365
  const rows = await scopedDb.select().from(sessions).where(eq(sessions.workspaceId, workspaceId)).orderBy(desc(sessions.createdAt), desc(sessions.id)).limit(limit);
2190
- return rows.map(mapSession);
2366
+ const grouped = await sessionMcpServerMetadataForSessions(scopedDb, workspaceId, rows.map((row) => row.id));
2367
+ return rows.map((row) => mapSession(row, grouped.get(row.id) ?? []));
2191
2368
  });
2192
2369
  }
2193
2370
  async function countActiveSessionsForWorkspace(db, workspaceId) {
@@ -2208,12 +2385,38 @@ async function requireSession(db, workspaceId, sessionId) {
2208
2385
  }
2209
2386
  return session;
2210
2387
  }
2211
- async function listSessionEvents(db, workspaceId, sessionId, after = 0, limit = 500) {
2388
+ var POSTGRES_INT_MAX = 2147483647;
2389
+ async function listSessionEvents(db, workspaceId, sessionId, afterOrOptions = 0, legacyLimit = 500) {
2390
+ const options = typeof afterOrOptions === "number" ? { after: afterOrOptions, limit: legacyLimit } : afterOrOptions;
2391
+ const after = normalizeEventSequence(options.after, 0);
2392
+ const limit = normalizeEventLimit(options.limit, 500);
2393
+ const hasBefore = options.before !== void 0 && Number.isFinite(options.before);
2394
+ const before = hasBefore ? Math.floor(options.before) : void 0;
2212
2395
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2213
- const rows = await scopedDb.select().from(sessionEvents).where(and(eq(sessionEvents.workspaceId, workspaceId), eq(sessionEvents.sessionId, sessionId), gt(sessionEvents.sequence, after))).orderBy(asc(sessionEvents.sequence)).limit(limit);
2214
- return rows.map(mapEvent);
2396
+ const filters = [
2397
+ eq(sessionEvents.workspaceId, workspaceId),
2398
+ eq(sessionEvents.sessionId, sessionId),
2399
+ gt(sessionEvents.sequence, after)
2400
+ ];
2401
+ if (before !== void 0 && before <= POSTGRES_INT_MAX) {
2402
+ filters.push(lt(sessionEvents.sequence, before));
2403
+ }
2404
+ const rows = await scopedDb.select().from(sessionEvents).where(and(...filters)).orderBy(hasBefore ? desc(sessionEvents.sequence) : asc(sessionEvents.sequence)).limit(limit);
2405
+ return (hasBefore ? rows.reverse() : rows).map(mapEvent);
2215
2406
  });
2216
2407
  }
2408
+ function normalizeEventSequence(value, fallback) {
2409
+ if (value === void 0 || !Number.isFinite(value)) {
2410
+ return fallback;
2411
+ }
2412
+ return Math.floor(value);
2413
+ }
2414
+ function normalizeEventLimit(value, fallback) {
2415
+ if (value === void 0 || !Number.isFinite(value)) {
2416
+ return fallback;
2417
+ }
2418
+ return Math.max(0, Math.floor(value));
2419
+ }
2217
2420
  async function getSessionEvent(db, workspaceId, eventId) {
2218
2421
  return await withWorkspaceRls(db, workspaceId, async (scopedDb) => {
2219
2422
  const [row] = await scopedDb.select().from(sessionEvents).where(and(eq(sessionEvents.workspaceId, workspaceId), eq(sessionEvents.id, eventId))).limit(1);
@@ -2348,6 +2551,20 @@ async function applyContextCompaction(db, input) {
2348
2551
  eq(sessionHistoryItems.active, true),
2349
2552
  lt(sessionHistoryItems.position, input.boundaryPosition)
2350
2553
  ));
2554
+ if (input.replacementItems && input.replacementItems.length > 0) {
2555
+ await tx.insert(sessionHistoryItems).values(input.replacementItems.map((entry) => ({
2556
+ accountId: input.accountId,
2557
+ workspaceId: input.workspaceId,
2558
+ sessionId: input.sessionId,
2559
+ turnId: null,
2560
+ position: entry.position,
2561
+ item: sanitizeEventPayload(entry.item),
2562
+ active: true
2563
+ }))).onConflictDoUpdate({
2564
+ target: [sessionHistoryItems.workspaceId, sessionHistoryItems.sessionId, sessionHistoryItems.position],
2565
+ set: { active: true }
2566
+ });
2567
+ }
2351
2568
  await tx.insert(sessionHistoryItems).values({
2352
2569
  accountId: input.accountId,
2353
2570
  workspaceId: input.workspaceId,
@@ -4482,7 +4699,9 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
4482
4699
  if (!sessionRow) {
4483
4700
  throw new Error(`Session not found: ${sessionId}`);
4484
4701
  }
4485
- const built = build(mapSession(sessionRow));
4702
+ const built = await build(mapSession(sessionRow), {
4703
+ updateSessionMcpServerCredentials: async (updates) => await updateSessionMcpServerCredentialsInTransaction(tx, { workspaceId, sessionId, updates })
4704
+ });
4486
4705
  if (built.events.length === 0) {
4487
4706
  return [];
4488
4707
  }
@@ -4519,7 +4738,7 @@ async function appendSessionEventsWithLockedSessionUpdate(db, workspaceId, sessi
4519
4738
  function sessionSubject(workspaceId, sessionId) {
4520
4739
  return `workspaces.${workspaceId}.sessions.${sessionId}.events`;
4521
4740
  }
4522
- function mapSession(row) {
4741
+ function mapSession(row, mcpServers = []) {
4523
4742
  return {
4524
4743
  id: row.id,
4525
4744
  accountId: row.accountId,
@@ -4542,6 +4761,7 @@ function mapSession(row) {
4542
4761
  activeEpoch: Number(row.activeEpoch),
4543
4762
  environmentId: row.environmentId,
4544
4763
  firstPartyMcpPermissions: row.firstPartyMcpPermissions ?? null,
4764
+ mcpServers,
4545
4765
  parentSessionId: row.parentSessionId ?? null,
4546
4766
  createIdempotencyKey: row.createIdempotencyKey ?? null,
4547
4767
  temporalWorkflowId: row.temporalWorkflowId,
@@ -4962,6 +5182,7 @@ export {
4962
5182
  createScheduledTaskRun,
4963
5183
  createSession,
4964
5184
  createSessionGoal,
5185
+ createSessionMcpServers,
4965
5186
  createSessionWithIdempotencyKey,
4966
5187
  createSocialConnection,
4967
5188
  createSocialPost,
@@ -5062,6 +5283,8 @@ export {
5062
5283
  listScheduledTasks,
5063
5284
  listSessionEvents,
5064
5285
  listSessionIdsInGroup,
5286
+ listSessionMcpServerMetadata,
5287
+ listSessionMcpServersForRun,
5065
5288
  listSessionTurns,
5066
5289
  listSessions,
5067
5290
  listSocialConnections,
@@ -5145,6 +5368,7 @@ export {
5145
5368
  updateScheduledTask,
5146
5369
  updateScheduledTaskRun,
5147
5370
  updateSessionGoal,
5371
+ updateSessionMcpServerCredentials,
5148
5372
  updateSessionTitle,
5149
5373
  updateWorkspace,
5150
5374
  updateWorkspaceEnvironment,