@opengeni/core 0.24.1 → 0.28.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.
@@ -0,0 +1,88 @@
1
+ import {
2
+ FIKEN_CREDENTIAL_LABEL,
3
+ FIKEN_CREDENTIAL_ROLE,
4
+ FIKEN_PROVIDER_DOMAIN,
5
+ FikenConnectionMetadata,
6
+ type ConnectionMetadata,
7
+ type FikenCompanySummary,
8
+ type FikenConnectionMetadata as FikenMetadata,
9
+ } from "@opengeni/contracts";
10
+
11
+ export function fikenConnectionMetadata(metadata: Record<string, unknown>): FikenMetadata | null {
12
+ const parsed = FikenConnectionMetadata.safeParse(metadata);
13
+ return parsed.success ? parsed.data : null;
14
+ }
15
+
16
+ /**
17
+ * A workspace-shared Fiken connection created through one of the verified
18
+ * install paths: the pasted personal API token (`api_key`) or the Fiken OAuth
19
+ * app flow (`oauth2`). Both are deliberately workspace-owned; personal
20
+ * ("Connect only for me") ownership needs the delegation-snapshot lane and is
21
+ * not yet wired for the first-party fiken tools.
22
+ */
23
+ export function isFikenConnection(connection: ConnectionMetadata): boolean {
24
+ return (
25
+ connection.subjectId === null &&
26
+ connection.providerDomain === FIKEN_PROVIDER_DOMAIN &&
27
+ (connection.kind === "api_key" || connection.kind === "oauth2") &&
28
+ fikenConnectionMetadata(connection.metadata)?.credentialRole === FIKEN_CREDENTIAL_ROLE
29
+ );
30
+ }
31
+
32
+ /**
33
+ * The Fiken credential role is reserved for the verified install routes.
34
+ * Generic connection create/update must reject it so a caller cannot forge a
35
+ * "verified" Fiken row (with attacker-chosen companies metadata) that the
36
+ * first-party fiken tools would then bind to. Mirrors the Slack bot guard.
37
+ */
38
+ export function hasReservedFikenMetadata(
39
+ metadata: Record<string, unknown> | null | undefined,
40
+ ): boolean {
41
+ return (
42
+ metadata?.credentialRole === FIKEN_CREDENTIAL_ROLE ||
43
+ metadata?.credentialLabel === FIKEN_CREDENTIAL_LABEL
44
+ );
45
+ }
46
+
47
+ /**
48
+ * One default-company rule for both verified lanes: an explicit request wins
49
+ * when the credential can access it, then a still-accessible previous default
50
+ * survives reconnect, then a single-company credential auto-selects. Returns
51
+ * null (caller decides whether that is an error) when a requested slug is not
52
+ * among the verified companies.
53
+ */
54
+ export function resolveFikenDefaultCompanySlug(input: {
55
+ requested: string | null;
56
+ previous: string | null;
57
+ companies: readonly FikenCompanySummary[];
58
+ }): string | null {
59
+ const accessible = (slug: string | null): slug is string =>
60
+ slug !== null && input.companies.some((company) => company.slug === slug);
61
+ if (input.requested !== null) {
62
+ return accessible(input.requested) ? input.requested : null;
63
+ }
64
+ if (accessible(input.previous)) {
65
+ return input.previous;
66
+ }
67
+ return input.companies.length === 1 ? input.companies[0]!.slug : null;
68
+ }
69
+
70
+ /**
71
+ * The row a capability tile or tool default should bind to when several Fiken
72
+ * connections exist: usable status first, then newest update, then immutable
73
+ * UUID descending as the stable tie-breaker.
74
+ */
75
+ export function preferredFikenConnection<
76
+ T extends Pick<ConnectionMetadata, "status" | "updatedAt" | "id">,
77
+ >(connections: readonly T[]): T | null {
78
+ const statusRank = (status: ConnectionMetadata["status"]): number =>
79
+ status === "active" ? 0 : status === "needs_reauth" ? 1 : 2;
80
+ return (
81
+ [...connections].sort(
82
+ (left, right) =>
83
+ statusRank(left.status) - statusRank(right.status) ||
84
+ right.updatedAt.localeCompare(left.updatedAt) ||
85
+ right.id.localeCompare(left.id),
86
+ )[0] ?? null
87
+ );
88
+ }
@@ -7,6 +7,7 @@ import {
7
7
  import {
8
8
  aggregateModelCallFacts,
9
9
  aggregateModelCallFactsByDay,
10
+ aggregateModelCallFactsByHour,
10
11
  aggregateRootSessionDrivers,
11
12
  aggregateScheduleFacts,
12
13
  aggregateSessionDepth,
@@ -15,6 +16,7 @@ import {
15
16
  countScheduledTaskFires,
16
17
  countSessionsAttachedToGroups,
17
18
  enumerateUtcDays,
19
+ enumerateUtcHours,
18
20
  listFloorSessions,
19
21
  listLiveWarmLeases,
20
22
  listModelCallFacets,
@@ -23,6 +25,7 @@ import {
23
25
  requireWorkspace,
24
26
  sumUsageQuantity,
25
27
  sumUsageQuantityByDay,
28
+ sumUsageQuantityByHour,
26
29
  sumUsageQuantityInRange,
27
30
  type Database,
28
31
  } from "@opengeni/db";
@@ -82,29 +85,29 @@ function resolveRangeWindow(
82
85
  since = startOfUtcDay(now);
83
86
  rangeLabel = "Today (UTC)";
84
87
  priorLabel = "Prior equal window";
85
- seriesLabel = "Credit $ (UTC day)";
86
- cacheSeriesLabel = "Cache hit %";
88
+ seriesLabel = "Credit $ / UTC hour";
89
+ cacheSeriesLabel = "Cache hit % / UTC hour";
87
90
  break;
88
91
  case "week":
89
92
  since = new Date(startOfUtcDay(now).getTime() - 6 * 24 * 60 * 60 * 1000);
90
93
  rangeLabel = "Last 7 days (UTC)";
91
94
  priorLabel = "Prior 7 days";
92
- seriesLabel = "Credit $ / day";
93
- cacheSeriesLabel = "Cache hit % / day";
95
+ seriesLabel = "Credit $ / UTC day";
96
+ cacheSeriesLabel = "Cache hit % / UTC day";
94
97
  break;
95
98
  case "month":
96
99
  since = startOfUtcMonth(now);
97
100
  rangeLabel = "This month (UTC)";
98
101
  priorLabel = "Prior equal window";
99
- seriesLabel = "Credit $ / day";
100
- cacheSeriesLabel = "Cache hit % / day";
102
+ seriesLabel = "Credit $ / UTC day";
103
+ cacheSeriesLabel = "Cache hit % / UTC day";
101
104
  break;
102
105
  case "ytd":
103
106
  since = startOfUtcYear(now);
104
107
  rangeLabel = "Year to date (UTC)";
105
108
  priorLabel = "Prior equal window";
106
- seriesLabel = "Credit $ / day";
107
- cacheSeriesLabel = "Cache hit % / day";
109
+ seriesLabel = "Credit $ / UTC day";
110
+ cacheSeriesLabel = "Cache hit % / UTC day";
108
111
  break;
109
112
  default: {
110
113
  const _exhaustive: never = range;
@@ -182,6 +185,10 @@ export async function getWorkspaceInsights(
182
185
  const model = input.model?.trim() || null;
183
186
  const modelFilterActive = Boolean(provider || model);
184
187
  const filter = { provider, model };
188
+ const aggregateFactsForSeries =
189
+ input.range === "today" ? aggregateModelCallFactsByHour : aggregateModelCallFactsByDay;
190
+ const sumUsageForSeries =
191
+ input.range === "today" ? sumUsageQuantityByHour : sumUsageQuantityByDay;
185
192
 
186
193
  const [
187
194
  workspaceCreditMicros,
@@ -242,19 +249,19 @@ export async function getWorkspaceInsights(
242
249
  until: window.priorUntil,
243
250
  ...filter,
244
251
  }),
245
- aggregateModelCallFactsByDay(db, {
252
+ aggregateFactsForSeries(db, {
246
253
  workspaceId: input.workspaceId,
247
254
  since: window.since,
248
255
  until: window.until,
249
256
  ...filter,
250
257
  }),
251
- sumUsageQuantityByDay(db, {
258
+ sumUsageForSeries(db, {
252
259
  workspaceId: input.workspaceId,
253
260
  eventType: "sandbox.warm_seconds",
254
261
  since: window.since,
255
262
  until: window.until,
256
263
  }),
257
- sumUsageQuantityByDay(db, {
264
+ sumUsageForSeries(db, {
258
265
  workspaceId: input.workspaceId,
259
266
  eventType: "model.cost",
260
267
  since: window.since,
@@ -382,9 +389,12 @@ export async function getWorkspaceInsights(
382
389
  const priorCacheInputTokens = priorModelRows.reduce((sum, row) => sum + row.cacheInputTokens, 0);
383
390
  const priorCalls = priorModelRows.reduce((sum, row) => sum + row.calls, 0);
384
391
 
385
- const days = enumerateUtcDays(window.since, window.until);
386
- const series = days.map((day) => {
387
- const facts = factDays.get(day) ?? {
392
+ const buckets =
393
+ input.range === "today"
394
+ ? enumerateUtcHours(window.since, window.until)
395
+ : enumerateUtcDays(window.since, window.until);
396
+ const series = buckets.map((bucket) => {
397
+ const facts = factDays.get(bucket) ?? {
388
398
  costMicros: 0,
389
399
  estimatedProviderCostMicros: 0,
390
400
  estimatedProviderCostKnownCalls: 0,
@@ -401,13 +411,13 @@ export async function getWorkspaceInsights(
401
411
  };
402
412
  const modelCostMicros = modelFilterActive
403
413
  ? facts.costMicros
404
- : (costDays.get(day) ?? facts.costMicros);
414
+ : (costDays.get(bucket) ?? facts.costMicros);
405
415
  return {
406
- label: day.slice(5),
416
+ label: input.range === "today" ? bucket.slice(11) : bucket.slice(5),
407
417
  modelCostUsd: microsToUsd(modelCostMicros),
408
418
  estimatedProviderUsd: microsToUsd(facts.estimatedProviderCostMicros),
409
419
  estimatedProviderCostKnownCalls: facts.estimatedProviderCostKnownCalls,
410
- warmSeconds: warmDays.get(day) ?? 0,
420
+ warmSeconds: warmDays.get(bucket) ?? 0,
411
421
  inputTokens: facts.inputTokens,
412
422
  outputTokens: facts.outputTokens,
413
423
  cachedTokens: facts.cachedTokens,
@@ -10,6 +10,7 @@ import type {
10
10
  SessionAuthorizationSurface,
11
11
  CreateScheduledTaskRequest as CreateScheduledTaskPayload,
12
12
  UpdateScheduledTaskRequest as UpdateScheduledTaskPayload,
13
+ XaiProviderAccountAuthoritySnapshotV1,
13
14
  } from "@opengeni/contracts";
14
15
  import { OPENGENI_SLACK_BOT_SESSION_METADATA_KEY } from "@opengeni/contracts";
15
16
  import {
@@ -21,10 +22,12 @@ import {
21
22
  getRig,
22
23
  getScheduledTask,
23
24
  getScheduledTaskPersonalConnectionDelegations,
25
+ getSessionTurnXaiProviderAccountAuthoritySnapshot,
24
26
  getSession,
25
27
  requireWorkspace,
26
28
  scopedKnowledgeScopeKey,
27
29
  updateScheduledTask,
30
+ resolveXaiProviderAccountAuthoritySnapshotForAcceptance,
28
31
  type Database,
29
32
  type UpdateScheduledTaskInput,
30
33
  } from "@opengeni/db";
@@ -161,6 +164,18 @@ export async function createValidatedScheduledTask(input: {
161
164
  source: personalConnectionDelegationSourceForGrant(input.grant),
162
165
  });
163
166
  const creationInitiator = creationInitiatorForGrant(input.grant);
167
+ const xaiProviderAccountAuthoritySnapshot: XaiProviderAccountAuthoritySnapshotV1 =
168
+ creationInitiator.actor
169
+ ? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
170
+ input.db,
171
+ input.grant.workspaceId,
172
+ creationInitiator.actor.sessionId,
173
+ creationInitiator.actor.turnId,
174
+ )
175
+ : await resolveXaiProviderAccountAuthoritySnapshotForAcceptance(input.db, {
176
+ workspaceId: input.grant.workspaceId,
177
+ subjectId: input.grant.subjectId,
178
+ });
164
179
  return await createScheduledTask(input.db, {
165
180
  id,
166
181
  accountId: input.grant.accountId,
@@ -177,6 +192,7 @@ export async function createValidatedScheduledTask(input: {
177
192
  ...(creationInitiator.context ? { createdByContext: creationInitiator.context } : {}),
178
193
  createdByActor: creationInitiator.actor ?? null,
179
194
  personalConnectionDelegations,
195
+ xaiProviderAccountAuthoritySnapshot,
180
196
  targetSessionId: target?.id ?? null,
181
197
  variableSetId: input.payload.variableSetId ?? null,
182
198
  rigId: input.payload.rigId ?? null,
@@ -2,9 +2,11 @@ import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
2
2
  import {
3
3
  canonicalizeConfiguredModelId,
4
4
  configuredAllowedModels,
5
+ resolveFirstPartyMcpToolPolicy,
5
6
  policyProviderIdForModel,
6
7
  resolveTurnExecutionPolicyV1,
7
8
  WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
9
+ XAI_SUBSCRIPTION_MODEL_ID_PREFIX,
8
10
  type Settings,
9
11
  } from "@opengeni/config";
10
12
  import {
@@ -46,6 +48,7 @@ import {
46
48
  type TurnInitiator,
47
49
  type TurnInitiatorContext,
48
50
  type TurnExecutionPolicyV1,
51
+ type XaiProviderAccountAuthoritySnapshotV1,
49
52
  } from "@opengeni/contracts";
50
53
  import {
51
54
  createSession,
@@ -53,6 +56,7 @@ import {
53
56
  encryptVariableSetValue,
54
57
  getAnySessionInGroup,
55
58
  getEnrollment,
59
+ getChannel,
56
60
  getRig,
57
61
  getWorkspaceDefaultRigId,
58
62
  listDistinctVariableSetIdsInGroup,
@@ -64,6 +68,7 @@ import {
64
68
  getWorkspaceControlEvent,
65
69
  getSessionLineage,
66
70
  getSessionTurn,
71
+ getSessionTurnXaiProviderAccountAuthoritySnapshot,
67
72
  getWorkspaceModelPolicy,
68
73
  initializeSessionStartAtomically,
69
74
  listSessionTurns,
@@ -98,7 +103,10 @@ import type {
98
103
  ApiRouteDeps,
99
104
  SessionWorkflowClient,
100
105
  } from "../dependencies";
101
- import { requireSessionAuthorization } from "../session-authorization";
106
+ import {
107
+ requireSessionAuthorization,
108
+ SessionAuthorizationDeniedError,
109
+ } from "../session-authorization";
102
110
  import { swapActiveSandbox, type FleetContext } from "../sandbox/fleet";
103
111
  import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
104
112
  import { validateSubmittedTimelineAnnotations } from "./timeline-annotations";
@@ -148,10 +156,18 @@ export class SessionSpawnDeniedError extends Error {
148
156
  export function resolveFirstPartyMcpToolsForCreate(
149
157
  requested: FirstPartyMcpToolName[] | undefined,
150
158
  parentStored: FirstPartyMcpToolName[] | null | undefined,
159
+ policy: {
160
+ default: readonly FirstPartyMcpToolName[];
161
+ allowed: readonly FirstPartyMcpToolName[];
162
+ } = {
163
+ default: DEFAULT_FIRST_PARTY_MCP_TOOLS,
164
+ allowed: FIRST_PARTY_MCP_TOOL_NAMES,
165
+ },
151
166
  ): FirstPartyMcpToolName[] {
152
167
  if (requested !== undefined) return [...requested];
153
- if (parentStored === undefined) return [...DEFAULT_FIRST_PARTY_MCP_TOOLS];
154
- return [...(parentStored ?? DEFAULT_FIRST_PARTY_MCP_TOOLS)];
168
+ const allowed = new Set(policy.allowed);
169
+ const inherited = parentStored === undefined ? policy.default : (parentStored ?? policy.default);
170
+ return [...inherited].filter((tool) => allowed.has(tool));
155
171
  }
156
172
 
157
173
  function sessionSpawnDeniedMessage(denial: SessionSpawnDenial): string {
@@ -540,7 +556,10 @@ export async function createAndStartSessionWithOutcome(input: {
540
556
  requestedSessionId?: string;
541
557
  db: Database;
542
558
  bus: EventBus;
543
- workflowClient: SessionWorkflowClient;
559
+ workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
560
+ /** Internal database-only composition seam. The exact session shell and this
561
+ * linkage commit together before its first event/turn can be initialized. */
562
+ beforeCreateCommit?: (tx: Database, sessionId: string) => Promise<void>;
544
563
  accountId: string;
545
564
  workspaceId: string;
546
565
  initialMessage: string;
@@ -572,6 +591,9 @@ export async function createAndStartSessionWithOutcome(input: {
572
591
  // rig promote never moves an existing session's version.
573
592
  rigId?: string | null;
574
593
  rigVersionId?: string | null;
594
+ // The workspace channel the session is filed under (rail organization only;
595
+ // resolved workspace-scoped by the caller). Null/omitted ⇒ unfiled (inbox).
596
+ channelId?: string | null;
575
597
  goal?: GoalSpec | null;
576
598
  // Per-session agent persona/system instructions (org-visible metadata, not a
577
599
  // secret). Persisted on the session row and composed system-level AFTER the
@@ -591,6 +613,7 @@ export async function createAndStartSessionWithOutcome(input: {
591
613
  mcpServers?: CreateSessionMcpServerInput[];
592
614
  sessionMcpServers?: SessionMcpServerMetadata[];
593
615
  personalConnectionDelegations?: McpPersonalConnectionDelegation[];
616
+ xaiProviderAccountAuthoritySnapshot?: XaiProviderAccountAuthoritySnapshotV1;
594
617
  // The manager session spawning this worker (a worker-signed sessionId claim
595
618
  // on the creating grant); null for direct API creates and scheduled runs.
596
619
  // When set, the worker's terminal-for-now transitions wake this parent.
@@ -665,6 +688,7 @@ export async function createAndStartSessionWithOutcome(input: {
665
688
  variableSetId: input.variableSet?.id ?? null,
666
689
  rigId: input.rigId ?? null,
667
690
  rigVersionId: input.rigVersionId ?? null,
691
+ channelId: input.channelId ?? null,
668
692
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
669
693
  firstPartyMcpTools: input.firstPartyMcpTools,
670
694
  instructions: input.instructions ?? null,
@@ -675,9 +699,15 @@ export async function createAndStartSessionWithOutcome(input: {
675
699
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
676
700
  mcpServers: input.mcpServers ?? [],
677
701
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
702
+ ...(input.xaiProviderAccountAuthoritySnapshot
703
+ ? {
704
+ initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
705
+ }
706
+ : {}),
678
707
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
679
708
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
680
709
  subjectId: input.subjectId ?? null,
710
+ ...(input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}),
681
711
  });
682
712
  if (keyedResult.denied) {
683
713
  throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(keyedResult.denial));
@@ -724,6 +754,7 @@ export async function createAndStartSessionWithOutcome(input: {
724
754
  variableSetId: input.variableSet?.id ?? null,
725
755
  rigId: input.rigId ?? null,
726
756
  rigVersionId: input.rigVersionId ?? null,
757
+ channelId: input.channelId ?? null,
727
758
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
728
759
  firstPartyMcpTools: input.firstPartyMcpTools,
729
760
  instructions: input.instructions ?? null,
@@ -733,9 +764,15 @@ export async function createAndStartSessionWithOutcome(input: {
733
764
  ...(input.sandboxOs ? { sandboxOs: input.sandboxOs } : {}),
734
765
  mcpServers: input.mcpServers ?? [],
735
766
  personalConnectionDelegations: input.personalConnectionDelegations ?? [],
767
+ ...(input.xaiProviderAccountAuthoritySnapshot
768
+ ? {
769
+ initialXaiProviderAccountAuthoritySnapshot: input.xaiProviderAccountAuthoritySnapshot,
770
+ }
771
+ : {}),
736
772
  maxNestedAgentDepthOverride: input.maxNestedAgentDepthOverride ?? null,
737
773
  allowNestedAgentDepthIncrease: input.allowNestedAgentDepthIncrease ?? false,
738
774
  subjectId: input.subjectId ?? null,
775
+ ...(input.beforeCreateCommit ? { beforeCreateCommit: input.beforeCreateCommit } : {}),
739
776
  });
740
777
  } catch (error) {
741
778
  if (error instanceof SessionSpawnDeniedDbError) {
@@ -769,7 +806,7 @@ async function finishStartSession(
769
806
  input: {
770
807
  db: Database;
771
808
  bus: EventBus;
772
- workflowClient: SessionWorkflowClient;
809
+ workflowClient: Pick<SessionWorkflowClient, "wakeSessionWorkflow">;
773
810
  initialMessage: string;
774
811
  deferInitialTurn?: boolean;
775
812
  turnInstructions?: string | null;
@@ -924,6 +961,16 @@ export function canonicalConfiguredModel(
924
961
  if (settings.codexSubscriptionEnabled && canonicalModel.startsWith(CODEX_MODEL_ID_PREFIX)) {
925
962
  return canonicalModel;
926
963
  }
964
+ // SuperGrok subscription models are also discovered per workspace rather
965
+ // than stored in the deployment-global allow-list. Connection availability
966
+ // is enforced by the workspace policy and worker; this edge guard only needs
967
+ // to admit the product-model namespace when the feature is enabled.
968
+ if (
969
+ settings.supergrokSubscriptionEnabled &&
970
+ canonicalModel.startsWith(XAI_SUBSCRIPTION_MODEL_ID_PREFIX)
971
+ ) {
972
+ return canonicalModel;
973
+ }
927
974
  if (canonicalModel.startsWith(WORKSPACE_GATEWAY_MODEL_ID_PREFIX)) {
928
975
  return canonicalModel;
929
976
  }
@@ -1270,11 +1317,18 @@ export async function createSessionForRequestWithOutcome(
1270
1317
  ? (grant.metadata["sessionId"] as string)
1271
1318
  : null;
1272
1319
  if (parentSessionId) {
1273
- await requireSessionAuthorization(deps, grant, {
1274
- sessionId: parentSessionId,
1275
- operation: "session.child.create",
1276
- surface: "core",
1277
- });
1320
+ try {
1321
+ await requireSessionAuthorization(deps, grant, {
1322
+ sessionId: parentSessionId,
1323
+ operation: "session.child.create",
1324
+ surface: "core",
1325
+ });
1326
+ } catch (error) {
1327
+ if (error instanceof SessionAuthorizationDeniedError) {
1328
+ throw new HTTPException(403, { message: error.message, cause: error });
1329
+ }
1330
+ throw error;
1331
+ }
1278
1332
  }
1279
1333
  const parentSession = parentSessionId ? await getSession(db, workspaceId, parentSessionId) : null;
1280
1334
  if (parentSessionId && !parentSession) {
@@ -1295,11 +1349,22 @@ export async function createSessionForRequestWithOutcome(
1295
1349
  message: "caller attempt does not belong to the parent session",
1296
1350
  });
1297
1351
  }
1352
+ const xaiProviderAccountAuthoritySnapshot =
1353
+ parentSession && creationInitiator.actor
1354
+ ? await getSessionTurnXaiProviderAccountAuthoritySnapshot(
1355
+ db,
1356
+ workspaceId,
1357
+ parentSession.id,
1358
+ creationInitiator.actor.turnId,
1359
+ )
1360
+ : undefined;
1298
1361
  const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
1299
1362
  db,
1300
1363
  workspaceId,
1301
1364
  settings,
1302
- { subjectId: grant.subjectId },
1365
+ {
1366
+ subjectId: grant.subjectId,
1367
+ },
1303
1368
  );
1304
1369
  const sessionMcpServers = hasOwnProperty(rawPayload, "mcpServers")
1305
1370
  ? validateSessionMcpServersForCreate(capabilityRuntimeSettings, grant, payload.mcpServers)
@@ -1429,6 +1494,20 @@ export async function createSessionForRequestWithOutcome(
1429
1494
  frozenRigVersionId = rig.activeVersion.id;
1430
1495
  }
1431
1496
  }
1497
+ // CHANNEL FILING. Pure rail organization: a UUID files the session into that
1498
+ // workspace channel, omission/null leaves it unfiled (inbox). Resolved
1499
+ // workspace-scoped so a foreign channel id can never attach; an explicit
1500
+ // unknown channelId is a caller error → 422.
1501
+ let channelId: string | null = null;
1502
+ if (payload.channelId) {
1503
+ const channel = await getChannel(db, workspaceId, payload.channelId);
1504
+ if (!channel) {
1505
+ throw new HTTPException(422, {
1506
+ message: `unknown channelId: ${payload.channelId}`,
1507
+ });
1508
+ }
1509
+ channelId = channel.id;
1510
+ }
1432
1511
  // A spawned worker is causally part of the exact turn that created it. Omitted
1433
1512
  // execution policy fields therefore inherit that calling turn rather than the
1434
1513
  // deployment defaults. This is especially important for Codex subscription
@@ -1548,12 +1627,22 @@ export async function createSessionForRequestWithOutcome(
1548
1627
  // Tool visibility is independent from permission authority. A child that
1549
1628
  // omits the field inherits the parent's exact effective selection; a
1550
1629
  // top-level omission selects the safe non-connector default catalog.
1630
+ const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(settings);
1631
+ const disallowedFirstPartyMcpTool = payload.firstPartyMcpTools?.find(
1632
+ (tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
1633
+ );
1634
+ if (disallowedFirstPartyMcpTool) {
1635
+ throw new HTTPException(422, {
1636
+ message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`,
1637
+ });
1638
+ }
1551
1639
  const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
1552
1640
  payload.firstPartyMcpTools,
1553
1641
  parentSession ? parentSession.firstPartyMcpTools : undefined,
1642
+ deploymentFirstPartyMcpToolPolicy,
1554
1643
  );
1555
1644
  if (payload.goal) {
1556
- const missingGoalTools = ["goal_update", "goal_complete", "goal_pause"].filter(
1645
+ const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
1557
1646
  (name) => !firstPartyMcpTools.includes(name as FirstPartyMcpToolName),
1558
1647
  );
1559
1648
  if (missingGoalTools.length > 0) {
@@ -1824,6 +1913,7 @@ export async function createSessionForRequestWithOutcome(
1824
1913
  // Frozen rig binding (M3): both null for a rig-less session (today's path).
1825
1914
  rigId: frozenRigId,
1826
1915
  rigVersionId: frozenRigVersionId,
1916
+ channelId,
1827
1917
  goal: payload.goal ?? null,
1828
1918
  // Per-session persona instructions (already trimmed/validated by the
1829
1919
  // contracts schema). Persisted on the row; composed system-level at turn
@@ -1835,6 +1925,7 @@ export async function createSessionForRequestWithOutcome(
1835
1925
  mcpServers: sessionMcpServers.dbServers,
1836
1926
  sessionMcpServers: sessionMcpServers.metadata,
1837
1927
  personalConnectionDelegations,
1928
+ ...(xaiProviderAccountAuthoritySnapshot ? { xaiProviderAccountAuthoritySnapshot } : {}),
1838
1929
  parentSessionId,
1839
1930
  createIdempotencyKey: payload.idempotencyKey ?? null,
1840
1931
  maxNestedAgentDepthOverride: payload.maxNestedAgentDepth ?? null,
@@ -1952,6 +2043,7 @@ export async function acceptSessionUserMessageWithOutcome(
1952
2043
  replay: boolean;
1953
2044
  }> {
1954
2045
  const { settings, db, bus, workflowClient, objectStorage } = deps;
2046
+ const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
1955
2047
  await requireSessionAuthorization(deps, grant, {
1956
2048
  sessionId,
1957
2049
  operation: input.delivery === "steer" ? "session.steer" : "session.append",
@@ -2031,7 +2123,6 @@ export async function acceptSessionUserMessageWithOutcome(
2031
2123
  tools: existingSession.tools,
2032
2124
  source: personalConnectionDelegationSourceForGrant(grant),
2033
2125
  });
2034
- const delegatedServiceInitiator = serviceInitiatorForGrant(grant);
2035
2126
  const { accepted, turn, interruptionCount, replay } = await postUserMessageTurn({
2036
2127
  db,
2037
2128
  bus,
@@ -2317,11 +2408,20 @@ export async function updateSessionToolPolicy(
2317
2408
  const explicitRequestedFirstPartyTools = explicitRequest
2318
2409
  ? [...explicitRequest.firstPartyMcpTools]
2319
2410
  : null;
2411
+ const deploymentFirstPartyMcpToolPolicy = resolveFirstPartyMcpToolPolicy(deps.settings);
2412
+ const disallowedFirstPartyMcpTool = explicitRequestedFirstPartyTools?.find(
2413
+ (tool) => !deploymentFirstPartyMcpToolPolicy.allowed.includes(tool),
2414
+ );
2415
+ if (disallowedFirstPartyMcpTool) {
2416
+ throw new HTTPException(422, {
2417
+ message: `first-party MCP tool is disabled by deployment policy: ${disallowedFirstPartyMcpTool}`,
2418
+ });
2419
+ }
2320
2420
  const workspaceDefaultTools = withFirstPartyTools(
2321
2421
  withDefaultEnabledCapabilityMcpTools([], deps.settings, capabilityRuntimeSettings),
2322
2422
  runtimeSettings,
2323
2423
  );
2324
- const workspaceDefaultFirstPartyTools = [...FIRST_PARTY_MCP_TOOL_NAMES];
2424
+ const workspaceDefaultFirstPartyTools = [...deploymentFirstPartyMcpToolPolicy.default];
2325
2425
  const events = await appendSessionEventsWithLockedSessionUpdate(
2326
2426
  deps.db,
2327
2427
  grant.workspaceId,
@@ -2353,9 +2453,12 @@ export async function updateSessionToolPolicy(
2353
2453
  : parent.tools,
2354
2454
  runtimeSettings,
2355
2455
  );
2456
+ const deploymentAllowedFirstPartyMcpTools = new Set(
2457
+ deploymentFirstPartyMcpToolPolicy.allowed,
2458
+ );
2356
2459
  const parentFirstPartyMcpTools = [
2357
- ...(parent.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS),
2358
- ];
2460
+ ...(parent.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default),
2461
+ ].filter((tool) => deploymentAllowedFirstPartyMcpTools.has(tool));
2359
2462
  if (requestedMode === "workspace_default") {
2360
2463
  if (!parentTracksWorkspaceDefaults) {
2361
2464
  throw new HTTPException(403, {
@@ -2409,7 +2512,8 @@ export async function updateSessionToolPolicy(
2409
2512
  const unchanged =
2410
2513
  stableJson({
2411
2514
  tools: session.tools,
2412
- firstPartyMcpTools: session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS,
2515
+ firstPartyMcpTools:
2516
+ session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default,
2413
2517
  policy: currentPolicy,
2414
2518
  }) ===
2415
2519
  stableJson({
@@ -2430,7 +2534,7 @@ export async function updateSessionToolPolicy(
2430
2534
  before: toolPolicyAuditSnapshot(
2431
2535
  session,
2432
2536
  session.tools,
2433
- [...(session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS)],
2537
+ [...(session.firstPartyMcpTools ?? deploymentFirstPartyMcpToolPolicy.default)],
2434
2538
  currentPolicy,
2435
2539
  ),
2436
2540
  after: toolPolicyAuditSnapshot(
@@ -1,4 +1,5 @@
1
1
  import {
2
+ GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
2
3
  SEEDANCE_2_5_MODEL_ID,
3
4
  VideoGenerationCapabilities,
4
5
  VideoGenerationPolicy,
@@ -23,11 +24,41 @@ export const VIDEO_GENERATION_MODEL_CATALOG: readonly VideoGenerationModelCapabi
23
24
  ]),
24
25
  resolutions: Object.freeze(["480p", "720p"]),
25
26
  aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16", "21:9", "adaptive"]),
26
- duration: Object.freeze({ minSeconds: 4, maxSeconds: 30, stepSeconds: 1 }),
27
+ duration: Object.freeze({
28
+ minSeconds: 4,
29
+ maxSeconds: 30,
30
+ stepSeconds: 1,
31
+ }),
32
+ supportsAudio: true,
33
+ }) as VideoGenerationModelCapability,
34
+ Object.freeze({
35
+ modelId: GROK_IMAGINE_VIDEO_1_5_MODEL_ID,
36
+ label: "Grok Imagine Video 1.5",
37
+ providerLabel: "Connected SuperGrok",
38
+ sourceModes: Object.freeze(["text", "first_frame", "image_reference"]),
39
+ resolutions: Object.freeze(["480p", "720p"]),
40
+ aspectRatios: Object.freeze(["16:9", "4:3", "1:1", "3:4", "9:16"]),
41
+ duration: Object.freeze({
42
+ minSeconds: 4,
43
+ maxSeconds: 15,
44
+ stepSeconds: 1,
45
+ }),
46
+ // xAI emits audio but exposes no separate audio wire option.
27
47
  supportsAudio: true,
28
48
  }) as VideoGenerationModelCapability,
29
49
  ]);
30
50
 
51
+ export function videoGenerationModelSupportsFundingSource(
52
+ modelId: string,
53
+ fundingSource: VideoGenerationPolicyType["fundingSource"],
54
+ ): boolean {
55
+ return modelId === GROK_IMAGINE_VIDEO_1_5_MODEL_ID
56
+ ? fundingSource === "supergrok_subscription"
57
+ : modelId === SEEDANCE_2_5_MODEL_ID
58
+ ? fundingSource === "opengeni_credits" || fundingSource === "workspace_gateway"
59
+ : false;
60
+ }
61
+
31
62
  export function defaultVideoGenerationPolicy(): VideoGenerationPolicyType {
32
63
  return VideoGenerationPolicy.parse({
33
64
  schemaVersion: 1,
@@ -44,7 +75,11 @@ export function videoGenerationCapabilitiesForPolicy(input: {
44
75
  }): VideoGenerationCapabilities {
45
76
  const policy = VideoGenerationPolicy.parse(input.policy);
46
77
  const enabled = new Set(policy.enabledModelIds);
47
- const models = VIDEO_GENERATION_MODEL_CATALOG.filter((model) => enabled.has(model.modelId));
78
+ const models = VIDEO_GENERATION_MODEL_CATALOG.filter(
79
+ (model) =>
80
+ enabled.has(model.modelId) &&
81
+ videoGenerationModelSupportsFundingSource(model.modelId, policy.fundingSource),
82
+ );
48
83
  if (models.length === 0 || policy.defaultModelId === null) {
49
84
  throw new Error("Video generation is disabled for this workspace");
50
85
  }
package/src/index.ts CHANGED
@@ -71,6 +71,8 @@ export * from "./domain/durable-learning-slack-publication";
71
71
  export * from "./domain/slack-publication-secret-safety";
72
72
  export * from "./domain/company-profile-durable-learning-adapter";
73
73
  export * from "./domain/slack-bot";
74
+ export * from "./domain/conversation-integrations";
75
+ export * from "./domain/fiken";
74
76
  export * from "./domain/workspace-members";
75
77
  export * from "./domain/video-generation";
76
78
  export * from "./domain/video-generation-capabilities";