@opengeni/core 0.12.7 → 0.14.3

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 (39) hide show
  1. package/dist/access/index.d.ts +22 -0
  2. package/dist/application/new-session-drafts.d.ts +14 -0
  3. package/dist/application/session-commands.d.ts +107 -0
  4. package/dist/billing/limits.d.ts +29 -0
  5. package/dist/dependencies.d.ts +137 -0
  6. package/dist/domain/capabilities.d.ts +62 -0
  7. package/dist/domain/environments.d.ts +33 -0
  8. package/dist/domain/insights.d.ts +11 -0
  9. package/dist/domain/packs.d.ts +27 -0
  10. package/dist/domain/resources.d.ts +32 -0
  11. package/dist/domain/scheduled-tasks.d.ts +72 -0
  12. package/dist/domain/session-tool-policy.d.ts +31 -0
  13. package/dist/domain/sessions.d.ts +256 -0
  14. package/dist/domain/slack-bot.d.ts +19 -0
  15. package/dist/domain/workspace-members.d.ts +34 -0
  16. package/dist/index.d.ts +23 -1189
  17. package/dist/index.js +758 -115
  18. package/dist/index.js.map +1 -1
  19. package/dist/managed-auth-type.d.ts +2 -0
  20. package/dist/rigs/index.d.ts +57 -0
  21. package/dist/sandbox/fleet.d.ts +197 -0
  22. package/dist/sandbox/routing.d.ts +55 -0
  23. package/dist/sandbox-types.d.ts +52 -0
  24. package/dist/session-authorization.d.ts +36 -0
  25. package/dist/transcription.d.ts +71 -0
  26. package/dist/workflow-wake-contract.d.ts +4 -0
  27. package/package.json +11 -11
  28. package/src/access/index.ts +73 -2
  29. package/src/application/new-session-drafts.ts +3 -0
  30. package/src/application/session-commands.ts +22 -9
  31. package/src/dependencies.ts +5 -0
  32. package/src/domain/insights.ts +480 -0
  33. package/src/domain/session-tool-policy.ts +22 -39
  34. package/src/domain/sessions.ts +140 -72
  35. package/src/domain/slack-bot.ts +2 -4
  36. package/src/index.ts +2 -0
  37. package/src/sandbox/fleet.ts +96 -33
  38. package/src/sandbox/routing.ts +29 -7
  39. package/src/transcription.ts +142 -0
@@ -10,7 +10,6 @@ import {
10
10
  } from "@opengeni/contracts";
11
11
  import type { Database } from "@opengeni/db";
12
12
  import { settingsWithEnabledCapabilityMcpServers } from "./capabilities";
13
- import { enabledCapabilityMcpToolRefs } from "./resources";
14
13
 
15
14
  const MANDATORY_SESSION_MCP_SERVER_IDS = ["opengeni"] as const;
16
15
  const PROJECTABLE_REGISTRY_ID = /^[A-Za-z0-9_-]+$/;
@@ -21,11 +20,8 @@ export type ResolvedSessionToolPolicy = {
21
20
  };
22
21
 
23
22
  export type SessionToolPolicyInput = {
24
- toolPolicy?: SessionToolPolicy | null;
23
+ toolPolicy: SessionToolPolicy;
25
24
  sessionTools: ToolRef[];
26
- turnTools?: ToolRef[];
27
- /** Undefined preserves the legacy merge path for pre-provenance callers. */
28
- turnToolsProvided?: boolean;
29
25
  availableMcpServerIds: Iterable<string>;
30
26
  /** Current omitted-tools defaults, intentionally narrower than all servers. */
31
27
  defaultMcpServerIds?: Iterable<string>;
@@ -35,6 +31,12 @@ function sortedIds(ids: Iterable<string>): string[] {
35
31
  return [...new Set(ids)].sort();
36
32
  }
37
33
 
34
+ /** Every configured runtime MCP defaults on; mandatory carrier IDs are separate. */
35
+ export function defaultSessionMcpServerIds(servers: Iterable<{ id: string }>): string[] {
36
+ const mandatory = new Set<string>(MANDATORY_SESSION_MCP_SERVER_IDS);
37
+ return sortedIds([...servers].map((server) => server.id).filter((id) => !mandatory.has(id)));
38
+ }
39
+
38
40
  function projectIds(ids: readonly string[]): { ids: string[]; truncated: boolean } {
39
41
  const projectable = ids.filter(
40
42
  (id) =>
@@ -52,33 +54,27 @@ function projectIds(ids: readonly string[]): { ids: string[]; truncated: boolean
52
54
  * Resolve the same ID-only policy used by API projections and worker turns.
53
55
  * This function never receives endpoint URLs, credentials, schemas, or live
54
56
  * probe results. `availableMcpServerIds` is the resolved runtime registry;
55
- * `defaultMcpServerIds` is the capability-only omitted-tools set.
57
+ * `defaultMcpServerIds` is the current configured omitted-tools default.
56
58
  */
57
59
  export function resolveSessionToolPolicy(input: SessionToolPolicyInput): ResolvedSessionToolPolicy {
58
- const policy = input.toolPolicy ?? { mode: "legacy" as const, inheritedFromSessionId: null };
60
+ const policy = input.toolPolicy;
59
61
  const availableIds = new Set(input.availableMcpServerIds);
60
- // Never infer omitted-tools defaults from the full runtime registry: static
61
- // MCPs are explicit-only unless they are capability-derived defaults.
62
62
  const defaultIds = new Set(input.defaultMcpServerIds ?? []);
63
63
  const mandatoryIds: string[] = MANDATORY_SESSION_MCP_SERVER_IDS.filter((id) =>
64
64
  availableIds.has(id),
65
65
  );
66
66
  const mandatoryIdSet = new Set<string>(mandatoryIds);
67
- const selectedRefs =
68
- input.turnToolsProvided === true
69
- ? mergeToolRefs([], input.turnTools ?? [])
70
- : input.turnToolsProvided === false
71
- ? mergeToolRefs([], input.sessionTools)
72
- : mergeToolRefs(input.sessionTools, input.turnTools ?? []);
73
- const tracksWorkspaceDefaults =
74
- policy.mode === "workspace_default" && input.turnToolsProvided !== true;
67
+ const selectedRefs = mergeToolRefs([], input.sessionTools);
68
+ const tracksWorkspaceDefaults = policy.mode === "workspace_default";
75
69
 
76
- // Optional capability refs are a historical materialization of a
77
- // workspace-default selection. They may outlive an installation or its
78
- // credentials; do not hand an unavailable optional ref to runtime, where it
79
- // would otherwise be an unknown MCP id. Strict historical refs intentionally
80
- // remain so their fail-loud compatibility contract is preserved.
81
- let toolRefs = selectedRefs.filter((tool) => tool.optional !== true || availableIds.has(tool.id));
70
+ // Persisted refs may outlive a capability installation, deployment config,
71
+ // or its credentials. Admission remains strict for newly requested refs, but
72
+ // turn-time materialization must not hand any no-longer-registered id to the
73
+ // runtime router: doing so fails before the model can respond and traps the
74
+ // session in an "Unknown MCP server id" loop. Keep the stale selection in the
75
+ // effective-policy projection below, while executable refs contain only the
76
+ // registry that is available for this exact turn.
77
+ let toolRefs = selectedRefs.filter((tool) => availableIds.has(tool.id));
82
78
  if (tracksWorkspaceDefaults) {
83
79
  toolRefs = mergeToolRefs(
84
80
  toolRefs,
@@ -168,19 +164,6 @@ export function resolveSessionToolPolicy(input: SessionToolPolicyInput): Resolve
168
164
  };
169
165
  }
170
166
 
171
- /**
172
- * Native provider tools that belong to the workspace-default capability set
173
- * follow the same omission/narrowing fence as deferred MCP tools. A durable
174
- * workspace-default policy receives them; fixed historical policies and an
175
- * explicit per-turn replacement do not. Provider support remains a separate
176
- * runtime gate and must also be true before a native tool is attached.
177
- */
178
- export function sessionToolPolicyAllowsDefaultNativeTools(
179
- policy: SessionEffectiveToolPolicy,
180
- ): boolean {
181
- return policy.mode === "workspace_default" && policy.lazyRouter.state === "required";
182
- }
183
-
184
167
  /** Current full runtime registry IDs, including configured static servers. */
185
168
  export async function workspaceSessionToolPolicyServerIds(
186
169
  db: Database,
@@ -191,14 +174,14 @@ export async function workspaceSessionToolPolicyServerIds(
191
174
  return sortedIds(runtimeSettings.mcpServers.map((server) => server.id));
192
175
  }
193
176
 
194
- /** Current omitted-tools defaults; this preserves capability-first behavior. */
177
+ /** Current omitted-tools defaults: every configured runtime MCP is on. */
195
178
  export async function workspaceSessionToolPolicyDefaultServerIds(
196
179
  db: Database,
197
180
  workspaceId: string,
198
181
  settings: Settings,
199
182
  ): Promise<string[]> {
200
183
  const runtimeSettings = await settingsWithEnabledCapabilityMcpServers(db, workspaceId, settings);
201
- return sortedIds(enabledCapabilityMcpToolRefs(settings, runtimeSettings).map((tool) => tool.id));
184
+ return defaultSessionMcpServerIds(runtimeSettings.mcpServers);
202
185
  }
203
186
 
204
187
  /** Add a bounded, secret-safe effective projection to a session response. */
@@ -214,7 +197,7 @@ export function sessionWithEffectiveToolPolicy(
214
197
  return {
215
198
  ...session,
216
199
  effectiveToolPolicy: resolveSessionToolPolicy({
217
- ...(session.toolPolicy ? { toolPolicy: session.toolPolicy } : {}),
200
+ toolPolicy: session.toolPolicy,
218
201
  sessionTools: session.tools,
219
202
  availableMcpServerIds: availableIds,
220
203
  defaultMcpServerIds: workspaceDefaultServerIds,
@@ -1,4 +1,4 @@
1
- import { CODEX_MODEL_ID_PREFIX } from "@opengeni/codex";
1
+ import { CODEX_MODEL_ID_PREFIX, isCodexBilledModel } from "@opengeni/codex";
2
2
  import {
3
3
  canonicalizeConfiguredModelId,
4
4
  configuredAllowedModels,
@@ -10,11 +10,13 @@ import {
10
10
  CreateSessionRequest,
11
11
  DEFAULT_FIRST_PARTY_MCP_PERMISSIONS,
12
12
  DEFAULT_FIRST_PARTY_MCP_TOOLS,
13
+ FIRST_PARTY_MCP_TOOL_NAMES,
13
14
  OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
14
15
  SessionSpawnDenial,
15
16
  ServiceTurnInitiator,
16
17
  ServiceTurnInitiatorContext,
17
18
  evaluateWorkspaceModelPolicy,
19
+ latencyModeForMetadata,
18
20
  reasoningEffortForMetadata,
19
21
  stableJson,
20
22
  type AccessGrant,
@@ -105,7 +107,6 @@ import {
105
107
  validateFileResources,
106
108
  validateGitHubRepositorySelection,
107
109
  validateToolRefs,
108
- validateToolRefsForSessionPolicy,
109
110
  withDefaultEnabledCapabilityMcpTools,
110
111
  } from "./resources";
111
112
 
@@ -131,16 +132,16 @@ export class SessionSpawnDeniedError extends Error {
131
132
 
132
133
  /**
133
134
  * Resolve per-session first-party tool visibility without consulting
134
- * authorization. Top-level omission uses the minimal runtime default (stored
135
- * as null); child omission snapshots the parent's exact effective selection.
136
- * Explicit [] is authoritative and must never widen.
135
+ * authorization. Top-level omission snapshots the complete runtime default;
136
+ * child omission snapshots the parent's exact effective selection. Explicit
137
+ * [] is authoritative and must never widen.
137
138
  */
138
139
  export function resolveFirstPartyMcpToolsForCreate(
139
140
  requested: FirstPartyMcpToolName[] | undefined,
140
141
  parentStored: FirstPartyMcpToolName[] | null | undefined,
141
- ): FirstPartyMcpToolName[] | null {
142
+ ): FirstPartyMcpToolName[] {
142
143
  if (requested !== undefined) return [...requested];
143
- if (parentStored === undefined) return null;
144
+ if (parentStored === undefined) return [...DEFAULT_FIRST_PARTY_MCP_TOOLS];
144
145
  return [...(parentStored ?? DEFAULT_FIRST_PARTY_MCP_TOOLS)];
145
146
  }
146
147
 
@@ -514,10 +515,12 @@ export async function createAndStartSession(input: {
514
515
  // Public admission always supplies provenance; optional keeps internal
515
516
  // callers that predate durable tool-policy provenance source-compatible
516
517
  // during the rolling deploy.
517
- toolPolicy?: SessionToolPolicy;
518
+ toolPolicy: SessionToolPolicy;
518
519
  clientEventId?: string;
519
520
  model: string;
520
521
  reasoningEffort: Settings["openaiReasoningEffort"];
522
+ /** Session default Fast/standard; mirrored into metadata when set. */
523
+ latencyMode?: "standard" | "priority" | "fast";
521
524
  turnExecutionPolicy: TurnExecutionPolicyV1;
522
525
  sandboxBackend: Settings["sandboxBackend"];
523
526
  metadata: Record<string, unknown>;
@@ -541,7 +544,7 @@ export async function createAndStartSession(input: {
541
544
  firstPartyMcpPermissions?: Permission[] | null;
542
545
  // Model-visible first-party tool names. Authorization remains controlled by
543
546
  // firstPartyMcpPermissions and the target resource checks.
544
- firstPartyMcpTools?: FirstPartyMcpToolName[] | null;
547
+ firstPartyMcpTools: FirstPartyMcpToolName[];
545
548
  // Encrypted DB rows plus matching safe metadata for create-time per-session
546
549
  // MCP servers. Metadata is the only shape emitted in events/responses.
547
550
  mcpServers?: CreateSessionMcpServerInput[];
@@ -587,6 +590,7 @@ export async function createAndStartSession(input: {
587
590
  ...input.metadata,
588
591
  model: input.model,
589
592
  reasoningEffort: input.reasoningEffort,
593
+ ...(input.latencyMode !== undefined ? { latencyMode: input.latencyMode } : {}),
590
594
  };
591
595
  // Keyed creation is intentionally handled only by the database admission
592
596
  // transaction below. Its workspace/key lock replays either the successful
@@ -602,7 +606,7 @@ export async function createAndStartSession(input: {
602
606
  resources: input.resources,
603
607
  skills: input.skills ?? [],
604
608
  tools: input.tools,
605
- ...(input.toolPolicy ? { toolPolicy: input.toolPolicy } : {}),
609
+ toolPolicy: input.toolPolicy,
606
610
  metadata: sessionMetadata,
607
611
  ...(input.createdBy ? { createdBy: input.createdBy } : {}),
608
612
  ...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
@@ -613,7 +617,7 @@ export async function createAndStartSession(input: {
613
617
  rigId: input.rigId ?? null,
614
618
  rigVersionId: input.rigVersionId ?? null,
615
619
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
616
- firstPartyMcpTools: input.firstPartyMcpTools ?? null,
620
+ firstPartyMcpTools: input.firstPartyMcpTools,
617
621
  instructions: input.instructions ?? null,
618
622
  parentSessionId: input.parentSessionId ?? null,
619
623
  createIdempotencyKey: input.createIdempotencyKey,
@@ -647,7 +651,7 @@ export async function createAndStartSession(input: {
647
651
  resources: input.resources,
648
652
  skills: input.skills ?? [],
649
653
  tools: input.tools,
650
- ...(input.toolPolicy ? { toolPolicy: input.toolPolicy } : {}),
654
+ toolPolicy: input.toolPolicy,
651
655
  metadata: sessionMetadata,
652
656
  ...(input.createdBy ? { createdBy: input.createdBy } : {}),
653
657
  ...(input.createdByContext ? { createdByContext: input.createdByContext } : {}),
@@ -658,7 +662,7 @@ export async function createAndStartSession(input: {
658
662
  rigId: input.rigId ?? null,
659
663
  rigVersionId: input.rigVersionId ?? null,
660
664
  firstPartyMcpPermissions: input.firstPartyMcpPermissions ?? null,
661
- firstPartyMcpTools: input.firstPartyMcpTools ?? null,
665
+ firstPartyMcpTools: input.firstPartyMcpTools,
662
666
  instructions: input.instructions ?? null,
663
667
  parentSessionId: input.parentSessionId ?? null,
664
668
  sandboxGroupId: input.sandboxGroupId ?? null,
@@ -692,7 +696,7 @@ async function finishStartSession(
692
696
  turnInstructions?: string | null;
693
697
  resources: ResourceRef[];
694
698
  tools: ToolRef[];
695
- toolPolicy?: SessionToolPolicy;
699
+ toolPolicy: SessionToolPolicy;
696
700
  clientEventId?: string;
697
701
  model: string;
698
702
  reasoningEffort: Settings["openaiReasoningEffort"];
@@ -750,7 +754,7 @@ async function finishStartSession(
750
754
  reasoningEffortFallback: input.reasoningEffort,
751
755
  turnExecutionPolicy: input.turnExecutionPolicy,
752
756
  createdEventPayload: {
753
- ...(input.toolPolicy ? { toolPolicy: input.toolPolicy } : {}),
757
+ toolPolicy: input.toolPolicy,
754
758
  ...(input.variableSet
755
759
  ? { variableSetId: input.variableSet.id, variableSetName: input.variableSet.name }
756
760
  : {}),
@@ -834,6 +838,36 @@ export function assertConfiguredModel(settings: Settings, model: string | null |
834
838
  canonicalConfiguredModel(settings, model);
835
839
  }
836
840
 
841
+ export const CODEX_COMPACTION_V2_PROVIDER_LOCKED = "codex_compaction_v2_provider_locked" as const;
842
+
843
+ /** Session is frozen on Codex remote compaction v2; non-Codex models are refused. */
844
+ export class CodexCompactionV2ProviderLockedError extends Error {
845
+ readonly code = CODEX_COMPACTION_V2_PROVIDER_LOCKED;
846
+ readonly productModelId: string;
847
+
848
+ constructor(productModelId: string) {
849
+ super(
850
+ `session is locked to Codex remote compaction v2; model "${productModelId}" is not a Codex subscription model`,
851
+ );
852
+ this.name = "CodexCompactionV2ProviderLockedError";
853
+ this.productModelId = productModelId;
854
+ }
855
+ }
856
+
857
+ /**
858
+ * Fail closed when a remote_v2 session would run a non-Codex product model.
859
+ * Portable sessions and non-Codex sessions keep free mid-session provider swap.
860
+ */
861
+ export function assertSessionAllowsProductModel(
862
+ session: Pick<Session, "codexCompactionMode">,
863
+ productModelId: string | null | undefined,
864
+ ): void {
865
+ if (productModelId === null || productModelId === undefined) return;
866
+ if (session.codexCompactionMode !== "remote_v2") return;
867
+ if (isCodexBilledModel(productModelId)) return;
868
+ throw new CodexCompactionV2ProviderLockedError(productModelId);
869
+ }
870
+
837
871
  /**
838
872
  * Reject a model the WORKSPACE's model policy blocks, at the same choke points
839
873
  * as assertConfiguredModel — a 422 at the edge instead of a queued turn the
@@ -902,6 +936,13 @@ export function reasoningEffortForSession(
902
936
  return reasoningEffortForMetadata(metadata, fallback);
903
937
  }
904
938
 
939
+ export function latencyModeForSession(
940
+ metadata: Record<string, unknown>,
941
+ fallback: "standard" | "priority" | "fast" = "standard",
942
+ ): "standard" | "priority" | "fast" {
943
+ return latencyModeForMetadata(metadata, fallback);
944
+ }
945
+
905
946
  /**
906
947
  * Appends a `user.message` to an existing session and enqueues the resulting
907
948
  * turn, merging requested resources/tools into the session and waking the
@@ -920,10 +961,9 @@ export async function postUserMessageTurn(input: {
920
961
  text: string;
921
962
  turnInstructions?: string | null;
922
963
  resources: ResourceRef[];
923
- tools: ToolRef[];
924
- toolsProvided: boolean;
925
964
  model?: string | null;
926
965
  reasoningEffort?: Settings["openaiReasoningEffort"] | null;
966
+ latencyMode?: "standard" | "priority" | "fast" | null;
927
967
  clientEventId?: string;
928
968
  mcpCredentialUpdates?: UpdateSessionMcpServerCredentialsInput[];
929
969
  delivery?: "send" | "steer";
@@ -943,6 +983,16 @@ export async function postUserMessageTurn(input: {
943
983
  // model inherits the session's model downstream (always a configured id).
944
984
  assertConfiguredModel(settings, requestedModel);
945
985
  await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, requestedModel);
986
+ const sessionForModelGate = await requireSession(db, workspaceId, sessionId);
987
+ const effectiveModelForGate = requestedModel ?? sessionForModelGate.model;
988
+ try {
989
+ assertSessionAllowsProductModel(sessionForModelGate, effectiveModelForGate);
990
+ } catch (error) {
991
+ if (error instanceof CodexCompactionV2ProviderLockedError) {
992
+ throw new HTTPException(422, { message: error.message, cause: error });
993
+ }
994
+ throw error;
995
+ }
946
996
  const operationKey = input.clientEventId ?? crypto.randomUUID();
947
997
  let result;
948
998
  try {
@@ -965,10 +1015,9 @@ export async function postUserMessageTurn(input: {
965
1015
  text: input.text,
966
1016
  turnInstructions: input.turnInstructions ?? null,
967
1017
  resources: input.resources,
968
- tools: input.tools,
969
- toolsProvided: input.toolsProvided,
970
1018
  model: requestedModel,
971
1019
  reasoningEffort: requestedReasoningEffort,
1020
+ latencyMode: input.latencyMode ?? null,
972
1021
  reasoningEffortFallback: input.reasoningEffortFallback ?? settings.openaiReasoningEffort,
973
1022
  turnExecutionPolicy: input.turnExecutionPolicy,
974
1023
  source: input.origin === "operator" ? "api" : "user",
@@ -1224,12 +1273,15 @@ export async function createSessionForRequest(
1224
1273
  // default-model session would otherwise be born blocked).
1225
1274
  await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
1226
1275
  const reasoningEffort = payload.reasoningEffort ?? settings.openaiReasoningEffort;
1276
+ const latencyMode = payload.latencyMode ?? "standard";
1227
1277
  const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
1228
1278
  modelId: model,
1229
1279
  requestedModelId: payload.model ?? null,
1230
1280
  modelSource: payload.model === undefined ? "deployment" : "explicit",
1231
1281
  reasoningEffort,
1232
1282
  reasoningSource: payload.reasoningEffort === undefined ? "deployment" : "explicit",
1283
+ latencyMode,
1284
+ latencyModeSource: payload.latencyMode === undefined ? "deployment" : "explicit",
1233
1285
  });
1234
1286
  // Parent linkage was resolved above, before context validation. A child with
1235
1287
  // no explicit permission override inherits the creating session's effective
@@ -1298,15 +1350,14 @@ export async function createSessionForRequest(
1298
1350
  }
1299
1351
  // Tool visibility is independent from permission authority. A child that
1300
1352
  // omits the field inherits the parent's exact effective selection; a
1301
- // top-level omission resolves to the fixed minimal default at execution.
1353
+ // top-level omission selects the complete catalog.
1302
1354
  const firstPartyMcpTools = resolveFirstPartyMcpToolsForCreate(
1303
1355
  payload.firstPartyMcpTools,
1304
1356
  parentSession ? parentSession.firstPartyMcpTools : undefined,
1305
1357
  );
1306
1358
  if (payload.goal) {
1307
- const effectiveTools = firstPartyMcpTools ?? [...DEFAULT_FIRST_PARTY_MCP_TOOLS];
1308
1359
  const missingGoalTools = ["goal_update", "goal_complete", "goal_pause"].filter(
1309
- (name) => !effectiveTools.includes(name as FirstPartyMcpToolName),
1360
+ (name) => !firstPartyMcpTools.includes(name as FirstPartyMcpToolName),
1310
1361
  );
1311
1362
  if (missingGoalTools.length > 0) {
1312
1363
  throw new HTTPException(422, {
@@ -1552,6 +1603,7 @@ export async function createSessionForRequest(
1552
1603
  ...(payload.clientEventId ? { clientEventId: payload.clientEventId } : {}),
1553
1604
  model,
1554
1605
  reasoningEffort,
1606
+ latencyMode,
1555
1607
  turnExecutionPolicy,
1556
1608
  // A shared spawn inherits the box's backend; a caller-supplied
1557
1609
  // sandboxBackend on a shared spawn is ignored (it is the same box). A
@@ -1636,8 +1688,7 @@ export async function createSessionForRequest(
1636
1688
  * `POST /sessions/:id/events` and the first-party MCP `session_send_message`
1637
1689
  * tool: resource/tool validation, usage limits, the locked append + turn
1638
1690
  * enqueue, and usage recording. `toolsProvided: false` durably preserves an
1639
- * absent `tools` key so execution inherits the session policy; an explicit
1640
- * empty array is a deliberate per-turn narrowing.
1691
+ * Tool selection is durable session state and never rides a follow-up prompt.
1641
1692
  */
1642
1693
  export async function acceptSessionUserMessage(
1643
1694
  deps: AcceptSessionUserMessageDependencies,
@@ -1648,10 +1699,9 @@ export async function acceptSessionUserMessage(
1648
1699
  text: string;
1649
1700
  turnInstructions?: string | null;
1650
1701
  resources?: ResourceRef[];
1651
- tools?: ToolRef[];
1652
- toolsProvided: boolean;
1653
1702
  model?: string | null;
1654
1703
  reasoningEffort?: ReasoningEffort | null;
1704
+ latencyMode?: "standard" | "priority" | "fast" | null;
1655
1705
  clientEventId?: string;
1656
1706
  mcpCredentialUpdates?: SessionMcpCredentialUpdateInput[];
1657
1707
  delivery?: "send" | "steer";
@@ -1660,23 +1710,12 @@ export async function acceptSessionUserMessage(
1660
1710
  expectedDraftRevision?: number | null;
1661
1711
  },
1662
1712
  ): Promise<{ accepted: SessionEvent; turn: SessionTurn }> {
1663
- if (input.toolsProvided && !deps.settings.sessionTurnToolReplacementEnabled) {
1664
- throw new HTTPException(503, {
1665
- message:
1666
- "explicit follow-up tool replacement is temporarily unavailable until provenance-aware turn workers finish rolling out; omit tools to inherit the session policy and retry",
1667
- });
1668
- }
1669
1713
  const { settings, db, bus, workflowClient, objectStorage } = deps;
1670
1714
  await requireSessionAuthorization(deps, grant, {
1671
1715
  sessionId,
1672
1716
  operation: input.delivery === "steer" ? "session.steer" : "session.append",
1673
1717
  surface: "core",
1674
1718
  });
1675
- const capabilityRuntimeSettings = await settingsWithEnabledCapabilityMcpServers(
1676
- db,
1677
- workspaceId,
1678
- settings,
1679
- );
1680
1719
  // Hoisted above requireLimit so the codex-billed predicate can resolve the
1681
1720
  // turn's effective model (a follow-up turn inherits the session's model). A
1682
1721
  // pure read with no side effects.
@@ -1687,43 +1726,31 @@ export async function acceptSessionUserMessage(
1687
1726
  if (effectiveModel === null) {
1688
1727
  throw new Error("effective follow-up model unexpectedly resolved to null");
1689
1728
  }
1729
+ try {
1730
+ assertSessionAllowsProductModel(existingSession, effectiveModel);
1731
+ } catch (error) {
1732
+ if (error instanceof CodexCompactionV2ProviderLockedError) {
1733
+ throw new HTTPException(422, { message: error.message, cause: error });
1734
+ }
1735
+ throw error;
1736
+ }
1690
1737
  const sessionReasoningEffort = reasoningEffortForSession(
1691
1738
  existingSession.metadata,
1692
1739
  settings.openaiReasoningEffort,
1693
1740
  );
1694
1741
  const effectiveReasoningEffort = input.reasoningEffort ?? sessionReasoningEffort;
1742
+ const sessionLatencyMode = latencyModeForSession(existingSession.metadata, "standard");
1743
+ const effectiveLatencyMode = input.latencyMode ?? sessionLatencyMode;
1695
1744
  const turnExecutionPolicy = resolveTurnExecutionPolicyV1(settings, {
1696
1745
  modelId: effectiveModel,
1697
1746
  requestedModelId: input.model ?? null,
1698
1747
  modelSource: input.model == null ? "session" : "explicit",
1699
1748
  reasoningEffort: effectiveReasoningEffort,
1700
1749
  reasoningSource: input.reasoningEffort == null ? "session" : "explicit",
1750
+ latencyMode: effectiveLatencyMode,
1751
+ latencyModeSource: input.latencyMode == null ? "session" : "explicit",
1701
1752
  });
1702
- const runtimeSettings = settingsWithSessionMcpServerMetadata(
1703
- capabilityRuntimeSettings,
1704
- existingSession.mcpServers,
1705
- );
1706
1753
  const requestedResources = normalizeResources(input.resources ?? []);
1707
- const tracksWorkspaceDefaults = existingSession.toolPolicy?.mode === "workspace_default";
1708
- const sessionPolicyTools = withFirstPartyTools(
1709
- tracksWorkspaceDefaults
1710
- ? withDefaultEnabledCapabilityMcpTools(
1711
- availableToolRefs(existingSession.tools, runtimeSettings),
1712
- settings,
1713
- capabilityRuntimeSettings,
1714
- )
1715
- : existingSession.tools,
1716
- runtimeSettings,
1717
- );
1718
- const validatedTools = input.toolsProvided
1719
- ? validateToolRefsForSessionPolicy({
1720
- requested: input.tools ?? [],
1721
- settings: runtimeSettings,
1722
- allowedTools: sessionPolicyTools,
1723
- message: "message tools may only narrow the session tool policy",
1724
- })
1725
- : [];
1726
- const requestedTools = input.toolsProvided ? validatedTools : [];
1727
1754
  await requireLimit(deps, {
1728
1755
  accountId: grant.accountId,
1729
1756
  workspaceId,
@@ -1757,10 +1784,9 @@ export async function acceptSessionUserMessage(
1757
1784
  text: input.text,
1758
1785
  turnInstructions: input.turnInstructions ?? null,
1759
1786
  resources: requestedResources,
1760
- tools: requestedTools,
1761
- toolsProvided: input.toolsProvided,
1762
1787
  model: input.model ?? null,
1763
1788
  reasoningEffort: input.reasoningEffort ?? null,
1789
+ latencyMode: input.latencyMode ?? null,
1764
1790
  reasoningEffortFallback: sessionReasoningEffort,
1765
1791
  turnExecutionPolicy,
1766
1792
  mcpCredentialUpdates,
@@ -1919,7 +1945,8 @@ export async function updateSessionMcpApprovalPolicy(
1919
1945
  function toolPolicyAuditSnapshot(
1920
1946
  session: Session,
1921
1947
  tools: ToolRef[],
1922
- policy = session.toolPolicy ?? { mode: "legacy" as const, inheritedFromSessionId: null },
1948
+ firstPartyMcpTools: FirstPartyMcpToolName[],
1949
+ policy = session.toolPolicy,
1923
1950
  ) {
1924
1951
  // Tool policy refs contain only public server ids and the optional/strict
1925
1952
  // execution mode; they never carry URLs, names, headers, credentials,
@@ -1951,6 +1978,8 @@ function toolPolicyAuditSnapshot(
1951
1978
  .map((tool) => tool.id),
1952
1979
  toolRefs,
1953
1980
  toolCount: allToolRefs.length,
1981
+ firstPartyMcpTools: [...firstPartyMcpTools].sort(),
1982
+ firstPartyMcpToolCount: firstPartyMcpTools.length,
1954
1983
  truncated: allToolRefs.length > toolRefs.length,
1955
1984
  };
1956
1985
  }
@@ -2004,10 +2033,14 @@ export async function updateSessionToolPolicy(
2004
2033
  return withFirstPartyTools(validatedTools, runtimeSettings);
2005
2034
  })()
2006
2035
  : null;
2036
+ const explicitRequestedFirstPartyTools = explicitRequest
2037
+ ? [...explicitRequest.firstPartyMcpTools]
2038
+ : null;
2007
2039
  const workspaceDefaultTools = withFirstPartyTools(
2008
2040
  withDefaultEnabledCapabilityMcpTools([], deps.settings, capabilityRuntimeSettings),
2009
2041
  runtimeSettings,
2010
2042
  );
2043
+ const workspaceDefaultFirstPartyTools = [...FIRST_PARTY_MCP_TOOL_NAMES];
2011
2044
  const events = await appendSessionEventsWithLockedSessionUpdate(
2012
2045
  deps.db,
2013
2046
  grant.workspaceId,
@@ -2019,6 +2052,7 @@ export async function updateSessionToolPolicy(
2019
2052
  }
2020
2053
 
2021
2054
  let nextTools: ToolRef[];
2055
+ let nextFirstPartyMcpTools: FirstPartyMcpToolName[];
2022
2056
  let nextPolicy: SessionToolPolicy;
2023
2057
  if (session.parentSessionId) {
2024
2058
  const parent = await context.getLockedSession(session.parentSessionId);
@@ -2036,6 +2070,9 @@ export async function updateSessionToolPolicy(
2036
2070
  : parent.tools,
2037
2071
  runtimeSettings,
2038
2072
  );
2073
+ const parentFirstPartyMcpTools = [
2074
+ ...(parent.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS),
2075
+ ];
2039
2076
  if (requestedMode === "workspace_default") {
2040
2077
  if (!parentTracksWorkspaceDefaults) {
2041
2078
  throw new HTTPException(403, {
@@ -2044,6 +2081,7 @@ export async function updateSessionToolPolicy(
2044
2081
  });
2045
2082
  }
2046
2083
  nextTools = parentEffective;
2084
+ nextFirstPartyMcpTools = parentFirstPartyMcpTools;
2047
2085
  nextPolicy = {
2048
2086
  mode: "workspace_default",
2049
2087
  inheritedFromSessionId: parent.id,
@@ -2055,6 +2093,16 @@ export async function updateSessionToolPolicy(
2055
2093
  parentEffective,
2056
2094
  "session tools may only narrow the parent session tool policy",
2057
2095
  );
2096
+ const parentFirstPartySet = new Set(parentFirstPartyMcpTools);
2097
+ const widenedFirstPartyTool = explicitRequestedFirstPartyTools!.find(
2098
+ (tool) => !parentFirstPartySet.has(tool),
2099
+ );
2100
+ if (widenedFirstPartyTool) {
2101
+ throw new HTTPException(403, {
2102
+ message: `session OpenGeni tools may only narrow the parent policy: ${widenedFirstPartyTool}`,
2103
+ });
2104
+ }
2105
+ nextFirstPartyMcpTools = explicitRequestedFirstPartyTools!;
2058
2106
  nextPolicy = {
2059
2107
  mode: "explicit",
2060
2108
  inheritedFromSessionId: parent.id,
@@ -2063,20 +2111,29 @@ export async function updateSessionToolPolicy(
2063
2111
  } else {
2064
2112
  nextTools =
2065
2113
  requestedMode === "workspace_default" ? workspaceDefaultTools : explicitRequestedTools!;
2114
+ nextFirstPartyMcpTools =
2115
+ requestedMode === "workspace_default"
2116
+ ? workspaceDefaultFirstPartyTools
2117
+ : explicitRequestedFirstPartyTools!;
2066
2118
  nextPolicy = { mode: requestedMode, inheritedFromSessionId: null };
2067
2119
  }
2068
2120
 
2069
- const currentPolicy = session.toolPolicy ?? {
2070
- mode: "legacy" as const,
2071
- inheritedFromSessionId: null,
2072
- };
2121
+ const currentPolicy = session.toolPolicy;
2073
2122
  // JSONB normalizes object-key order on the round trip, so plain
2074
2123
  // JSON.stringify would turn an identical retry into a second mutation
2075
2124
  // (and version bump) merely because the persisted key order differs from
2076
2125
  // the request object. Compare canonical JSON instead.
2077
2126
  const unchanged =
2078
- stableJson({ tools: session.tools, policy: currentPolicy }) ===
2079
- stableJson({ tools: nextTools, policy: nextPolicy });
2127
+ stableJson({
2128
+ tools: session.tools,
2129
+ firstPartyMcpTools: session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS,
2130
+ policy: currentPolicy,
2131
+ }) ===
2132
+ stableJson({
2133
+ tools: nextTools,
2134
+ firstPartyMcpTools: nextFirstPartyMcpTools,
2135
+ policy: nextPolicy,
2136
+ });
2080
2137
  if (unchanged) {
2081
2138
  return { events: [] };
2082
2139
  }
@@ -2087,8 +2144,18 @@ export async function updateSessionToolPolicy(
2087
2144
  {
2088
2145
  type: "session.tool_policy.updated" as const,
2089
2146
  payload: {
2090
- before: toolPolicyAuditSnapshot(session, session.tools, currentPolicy),
2091
- after: toolPolicyAuditSnapshot(session, nextTools, nextPolicy),
2147
+ before: toolPolicyAuditSnapshot(
2148
+ session,
2149
+ session.tools,
2150
+ [...(session.firstPartyMcpTools ?? DEFAULT_FIRST_PARTY_MCP_TOOLS)],
2151
+ currentPolicy,
2152
+ ),
2153
+ after: toolPolicyAuditSnapshot(
2154
+ session,
2155
+ nextTools,
2156
+ nextFirstPartyMcpTools,
2157
+ nextPolicy,
2158
+ ),
2092
2159
  version: nextVersion,
2093
2160
  effectiveFrom: "next_attempt",
2094
2161
  },
@@ -2096,6 +2163,7 @@ export async function updateSessionToolPolicy(
2096
2163
  ],
2097
2164
  update: {
2098
2165
  tools: nextTools,
2166
+ firstPartyMcpTools: nextFirstPartyMcpTools,
2099
2167
  toolPolicy: nextPolicy,
2100
2168
  toolPolicyVersion: nextVersion,
2101
2169
  expectedToolPolicyVersion: request.expectedVersion,
@@ -1,9 +1,9 @@
1
1
  import {
2
2
  OPENGENI_SLACK_BOT_CREDENTIAL_LABEL,
3
- OPENGENI_SLACK_BOT_REQUIRED_SCOPES,
4
3
  OPENGENI_SLACK_BOT_CREDENTIAL_ROLE,
5
4
  OPENGENI_SLACK_BOT_SESSION_METADATA_KEY,
6
5
  OpenGeniSlackBotConnectionMetadata,
6
+ areOpenGeniSlackBotScopesAccepted,
7
7
  type AccessGrant,
8
8
  type ConnectionMetadata,
9
9
  type OpenGeniSlackBotConnectionMetadata as OpenGeniSlackBotMetadata,
@@ -30,15 +30,13 @@ export function isOpenGeniSlackBotConnection(
30
30
  Pick<ConnectionMetadataWithVerification, "verifiedInstallAt" | "verifiedInstallVersion">
31
31
  >,
32
32
  ): boolean {
33
- const granted = new Set(connection.grantedScopes);
34
33
  return (
35
34
  connection.verifiedInstallAt != null &&
36
35
  connection.verifiedInstallVersion === connection.version &&
37
36
  connection.subjectId === null &&
38
37
  connection.providerDomain === "slack.com" &&
39
38
  connection.kind === "app_install" &&
40
- granted.size === OPENGENI_SLACK_BOT_REQUIRED_SCOPES.length &&
41
- OPENGENI_SLACK_BOT_REQUIRED_SCOPES.every((scope) => granted.has(scope)) &&
39
+ areOpenGeniSlackBotScopesAccepted(connection.grantedScopes) &&
42
40
  openGeniSlackBotMetadata(connection.metadata)?.credentialRole ===
43
41
  OPENGENI_SLACK_BOT_CREDENTIAL_ROLE
44
42
  );