@opengeni/core 3.0.1 → 3.1.2

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 (257) hide show
  1. package/README.md +4 -4
  2. package/dist/access/index.d.ts +131 -7
  3. package/dist/access/service-initiator.d.ts +3 -0
  4. package/dist/application/archived-session-imports.d.ts +6 -0
  5. package/dist/application/composer-submit.d.ts +1 -1
  6. package/dist/application/connect-operation.d.ts +6 -0
  7. package/dist/application/external-workspace-members.d.ts +13 -1
  8. package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
  9. package/dist/application/modal-native-original-configuration.d.ts +79 -0
  10. package/dist/application/modal-native-worker-host-transport.d.ts +297 -0
  11. package/dist/application/new-session-drafts.d.ts +29 -2
  12. package/dist/application/organization-privacy.d.ts +57 -0
  13. package/dist/application/prepared-mcp-connection.d.ts +31 -0
  14. package/dist/application/sandbox-recovery.d.ts +58 -0
  15. package/dist/application/session-commands.d.ts +3 -0
  16. package/dist/application/session-tenancy.d.ts +13 -4
  17. package/dist/billing/agent-run-admission.d.ts +21 -0
  18. package/dist/billing/limits.d.ts +27 -1
  19. package/dist/billing/model-call-admission.d.ts +34 -0
  20. package/dist/billing/model-usage-settlement.d.ts +112 -0
  21. package/dist/canonical-human-identities.js +1 -1
  22. package/dist/{chunk-VCZFFEPB.js → chunk-7T2SZ7BI.js} +16 -2
  23. package/dist/chunk-7T2SZ7BI.js.map +1 -0
  24. package/dist/{chunk-ZYCNLYGH.js → chunk-OMJFIIWK.js} +178 -12
  25. package/dist/chunk-OMJFIIWK.js.map +1 -0
  26. package/dist/{chunk-H7X52RI2.js → chunk-QI2M2B2K.js} +83 -8
  27. package/dist/chunk-QI2M2B2K.js.map +1 -0
  28. package/dist/{chunk-OF65T3PM.js → chunk-TTWTXQB2.js} +94 -6
  29. package/dist/chunk-TTWTXQB2.js.map +1 -0
  30. package/dist/codex-model-availability.d.ts +55 -0
  31. package/dist/default-session-model.d.ts +192 -0
  32. package/dist/dependencies.d.ts +41 -7
  33. package/dist/developer-setup-credential-ceiling.d.ts +10 -0
  34. package/dist/domain/agent-config-resolution.d.ts +49 -0
  35. package/dist/domain/automations.d.ts +8 -1
  36. package/dist/domain/capabilities.d.ts +112 -8
  37. package/dist/domain/connection-account-selection-error.d.ts +8 -0
  38. package/dist/domain/connector-tool-permissions.d.ts +62 -0
  39. package/dist/domain/direct-model-provider.d.ts +3 -0
  40. package/dist/domain/editable-artifacts/agent-application.d.ts +10 -1
  41. package/dist/domain/editable-artifacts/durable-export.d.ts +22 -1
  42. package/dist/domain/editable-artifacts/ports.d.ts +10 -0
  43. package/dist/domain/editable-artifacts/service.d.ts +3 -1
  44. package/dist/domain/editable-artifacts/types.d.ts +1 -1
  45. package/dist/domain/encryption-key.d.ts +12 -0
  46. package/dist/domain/insights-timing.d.ts +16 -0
  47. package/dist/domain/insights-usage.d.ts +27 -0
  48. package/dist/domain/insights.d.ts +21 -3
  49. package/dist/domain/knowledge-files.d.ts +1 -1
  50. package/dist/domain/knowledge-preparation.d.ts +10 -0
  51. package/dist/domain/knowledge-preparation.test.d.ts +1 -0
  52. package/dist/domain/knowledge-search.d.ts +15 -76
  53. package/dist/domain/mcp-account-bindings.d.ts +17 -0
  54. package/dist/domain/mcp-account-routes.d.ts +21 -0
  55. package/dist/domain/native-mcp-connection-admission.d.ts +3 -0
  56. package/dist/domain/organization-integration-catalog.d.ts +15 -0
  57. package/dist/domain/personal-connection-delegations.d.ts +73 -12
  58. package/dist/domain/pr-review.d.ts +121 -6
  59. package/dist/domain/realtime-voice-billing.d.ts +103 -0
  60. package/dist/domain/resources.d.ts +17 -1
  61. package/dist/domain/scheduled-task-access.d.ts +265 -0
  62. package/dist/domain/scheduled-task-account-audit.d.ts +27 -0
  63. package/dist/domain/scheduled-task-audience.d.ts +70 -0
  64. package/dist/domain/scheduled-tasks.d.ts +61 -18
  65. package/dist/domain/session-connection-accounts.d.ts +53 -0
  66. package/dist/domain/session-file-access.d.ts +4 -0
  67. package/dist/domain/session-native-connection-replacement.d.ts +14 -0
  68. package/dist/domain/session-start-timing.d.ts +7 -0
  69. package/dist/domain/session-tool-policy.d.ts +102 -4
  70. package/dist/domain/sessions.d.ts +179 -15
  71. package/dist/domain/site-audience.d.ts +52 -0
  72. package/dist/domain/skills.d.ts +29 -1
  73. package/dist/domain/slack-bot.d.ts +46 -1
  74. package/dist/domain/slack-publication-secret-safety.d.ts +1 -1
  75. package/dist/domain/vaults/index.d.ts +7 -0
  76. package/dist/domain/vaults/mapping.d.ts +25 -0
  77. package/dist/domain/vaults/provider-registry.d.ts +72 -0
  78. package/dist/domain/vaults/provider-sync.d.ts +176 -0
  79. package/dist/domain/vaults/provider-writes.d.ts +57 -0
  80. package/dist/domain/vaults/secret-resolver.d.ts +34 -0
  81. package/dist/domain/vaults/types.d.ts +24 -0
  82. package/dist/domain/vaults/vault-service.d.ts +35 -0
  83. package/dist/domain/voice-input-billing.d.ts +77 -0
  84. package/dist/domain/web-search-billing.d.ts +51 -0
  85. package/dist/domain/workspace-credits.d.ts +7 -0
  86. package/dist/editable-artifact-live/application.d.ts +2 -1
  87. package/dist/editable-artifact-live/server.d.ts +5 -1
  88. package/dist/editable-artifact-live/ticket.d.ts +2 -1
  89. package/dist/editable-artifact-live/types.d.ts +9 -0
  90. package/dist/editable-artifact-live.js +2 -2
  91. package/dist/editable-artifacts.js +10 -2
  92. package/dist/goal-admission.d.ts +627 -0
  93. package/dist/host-session-usage-limit.d.ts +15 -0
  94. package/dist/index.d.ts +43 -8
  95. package/dist/index.js +22682 -11743
  96. package/dist/index.js.map +1 -1
  97. package/dist/managed-session.d.ts +110 -7
  98. package/dist/model-call.d.ts +58 -0
  99. package/dist/model-catalog.d.ts +29 -3
  100. package/dist/model-provider-credentials.d.ts +43 -0
  101. package/dist/product-usage-metrics.d.ts +30 -0
  102. package/dist/rigs/index.d.ts +1 -13
  103. package/dist/sandbox/fleet.d.ts +58 -4
  104. package/dist/sandbox/routing.d.ts +42 -2
  105. package/dist/sandbox/runtime-settings.d.ts +27 -17
  106. package/dist/session-authorization.d.ts +10 -0
  107. package/dist/session-usage-limit-metrics.d.ts +60 -0
  108. package/dist/session-usage-limits.d.ts +89 -0
  109. package/dist/site-agent-clamp.d.ts +71 -0
  110. package/dist/site-session-origin.d.ts +39 -2
  111. package/dist/transcription.d.ts +120 -3
  112. package/dist/turn-surface.d.ts +20 -0
  113. package/dist/user-presence.d.ts +32 -0
  114. package/dist/workflow-wake-contract.d.ts +13 -1
  115. package/package.json +15 -22
  116. package/src/access/index.ts +961 -38
  117. package/src/access/service-initiator.ts +50 -0
  118. package/src/application/api-integration-servers.ts +9 -0
  119. package/src/application/archived-session-imports.ts +161 -0
  120. package/src/application/composer-submit.ts +3 -2
  121. package/src/application/connect-authority.ts +3 -3
  122. package/src/application/connect-operation.ts +37 -3
  123. package/src/application/external-continuation.ts +20 -3
  124. package/src/application/external-link-work-admission.ts +3 -0
  125. package/src/application/external-workspace-members.ts +51 -3
  126. package/src/application/modal-native-live-declaration-join.ts +196 -0
  127. package/src/application/modal-native-original-configuration.ts +261 -0
  128. package/src/application/modal-native-worker-host-transport.ts +263 -0
  129. package/src/application/new-session-drafts.ts +172 -55
  130. package/src/application/organization-privacy.ts +161 -0
  131. package/src/application/prepared-mcp-connection.ts +133 -0
  132. package/src/application/sandbox-recovery.ts +78 -0
  133. package/src/application/session-commands.ts +73 -1
  134. package/src/application/session-mcp-credential-rotation.ts +61 -18
  135. package/src/application/session-tenancy.ts +262 -44
  136. package/src/application/user-resource-grants.ts +1 -29
  137. package/src/billing/agent-run-admission.ts +117 -0
  138. package/src/billing/limits.ts +138 -14
  139. package/src/billing/model-call-admission.ts +118 -0
  140. package/src/billing/model-usage-settlement.ts +472 -0
  141. package/src/codex-model-availability.ts +264 -0
  142. package/src/default-session-model.ts +692 -0
  143. package/src/dependencies.ts +48 -6
  144. package/src/developer-setup-credential-ceiling.ts +60 -0
  145. package/src/domain/agent-config-resolution.ts +148 -0
  146. package/src/domain/automations.ts +76 -2
  147. package/src/domain/capabilities.ts +830 -181
  148. package/src/domain/connection-account-selection-error.ts +19 -0
  149. package/src/domain/connector-tool-permissions.ts +572 -0
  150. package/src/domain/direct-model-provider.ts +62 -0
  151. package/src/domain/editable-artifacts/agent-application.ts +44 -6
  152. package/src/domain/editable-artifacts/durable-export.ts +100 -5
  153. package/src/domain/editable-artifacts/in-memory.ts +6 -0
  154. package/src/domain/editable-artifacts/ports.ts +11 -0
  155. package/src/domain/editable-artifacts/service.ts +20 -0
  156. package/src/domain/editable-artifacts/types.ts +1 -1
  157. package/src/domain/encryption-key.ts +26 -0
  158. package/src/domain/file-owner.ts +16 -1
  159. package/src/domain/github-action-policies.ts +1 -1
  160. package/src/domain/github-skill-source.ts +3 -1
  161. package/src/domain/insights-timing.ts +95 -0
  162. package/src/domain/insights-usage.ts +67 -0
  163. package/src/domain/insights.ts +167 -43
  164. package/src/domain/knowledge-files.ts +10 -3
  165. package/src/domain/knowledge-messages.ts +1 -0
  166. package/src/domain/knowledge-preparation.test.ts +276 -0
  167. package/src/domain/knowledge-preparation.ts +147 -0
  168. package/src/domain/knowledge-search.test.ts +617 -5
  169. package/src/domain/knowledge-search.ts +240 -23
  170. package/src/domain/mcp-account-bindings.ts +169 -0
  171. package/src/domain/mcp-account-routes.ts +152 -0
  172. package/src/domain/native-mcp-connection-admission.ts +14 -0
  173. package/src/domain/organization-integration-catalog.ts +88 -0
  174. package/src/domain/personal-connection-delegations.ts +399 -257
  175. package/src/domain/pr-review.ts +24 -8
  176. package/src/domain/realtime-voice-billing.ts +425 -0
  177. package/src/domain/resources.ts +50 -4
  178. package/src/domain/scheduled-task-access.ts +1089 -0
  179. package/src/domain/scheduled-task-account-audit.ts +90 -0
  180. package/src/domain/scheduled-task-audience.ts +249 -0
  181. package/src/domain/scheduled-tasks.ts +1313 -374
  182. package/src/domain/session-connection-accounts.ts +239 -0
  183. package/src/domain/session-file-access.ts +47 -0
  184. package/src/domain/session-native-connection-replacement.ts +64 -0
  185. package/src/domain/session-start-timing.ts +60 -0
  186. package/src/domain/session-tool-policy.ts +489 -14
  187. package/src/domain/sessions.ts +2254 -717
  188. package/src/domain/site-audience.ts +130 -0
  189. package/src/domain/skills.ts +9 -0
  190. package/src/domain/slack-bot.ts +141 -4
  191. package/src/domain/slack-publication-secret-safety.ts +1 -1
  192. package/src/domain/vaults/index.ts +19 -0
  193. package/src/domain/vaults/mapping.ts +190 -0
  194. package/src/domain/vaults/provider-registry.ts +201 -0
  195. package/src/domain/vaults/provider-sync.ts +531 -0
  196. package/src/domain/vaults/provider-writes.ts +288 -0
  197. package/src/domain/vaults/secret-resolver.ts +464 -0
  198. package/src/domain/vaults/types.ts +26 -0
  199. package/src/domain/vaults/vault-service.ts +1316 -0
  200. package/src/domain/voice-input-billing.ts +330 -0
  201. package/src/domain/web-search-billing.ts +169 -0
  202. package/src/domain/workspace-credits.ts +13 -0
  203. package/src/editable-artifact-live/application.ts +8 -1
  204. package/src/editable-artifact-live/server.ts +154 -6
  205. package/src/editable-artifact-live/ticket.ts +86 -1
  206. package/src/editable-artifact-live/types.ts +10 -0
  207. package/src/goal-admission.ts +220 -0
  208. package/src/host-session-usage-limit.ts +25 -0
  209. package/src/index.ts +75 -11
  210. package/src/managed-session.ts +137 -6
  211. package/src/model-call.ts +79 -0
  212. package/src/model-catalog.ts +345 -91
  213. package/src/model-provider-credentials.ts +394 -0
  214. package/src/product-usage-metrics.ts +101 -0
  215. package/src/rigs/index.ts +23 -111
  216. package/src/sandbox/fleet.ts +253 -28
  217. package/src/sandbox/routing.ts +204 -43
  218. package/src/sandbox/runtime-settings.ts +159 -85
  219. package/src/session-authorization.ts +172 -23
  220. package/src/session-usage-limit-metrics.ts +95 -0
  221. package/src/session-usage-limits.ts +415 -0
  222. package/src/site-agent-clamp.ts +226 -0
  223. package/src/site-session-origin.ts +84 -5
  224. package/src/transcription.ts +123 -4
  225. package/src/turn-surface.ts +47 -0
  226. package/src/user-presence.ts +134 -0
  227. package/src/workflow-wake-contract.ts +14 -1
  228. package/dist/application/host-mcp-owner.d.ts +0 -6
  229. package/dist/application/host-mcp-resolvers.d.ts +0 -13
  230. package/dist/chunk-5WZYFL45.js +0 -266
  231. package/dist/chunk-5WZYFL45.js.map +0 -1
  232. package/dist/chunk-H7X52RI2.js.map +0 -1
  233. package/dist/chunk-OF65T3PM.js.map +0 -1
  234. package/dist/chunk-VCZFFEPB.js.map +0 -1
  235. package/dist/chunk-ZYCNLYGH.js.map +0 -1
  236. package/dist/domain/connection-authority.d.ts +0 -97
  237. package/dist/domain/connection-authority.js +0 -169
  238. package/dist/domain/connection-authority.js.map +0 -1
  239. package/dist/domain/environments.d.ts +0 -33
  240. package/dist/domain/host-mcp-authority-source-admission.d.ts +0 -10
  241. package/dist/domain/host-mcp-task-admission.d.ts +0 -18
  242. package/dist/domain/packs.d.ts +0 -59
  243. package/dist/domain/product-integration-pack.d.ts +0 -44
  244. package/dist/domain/product-integration-skill.gen.d.ts +0 -5
  245. package/dist/remote-mcp-credentials.d.ts +0 -23
  246. package/dist/remote-mcp-credentials.js +0 -11
  247. package/dist/remote-mcp-credentials.js.map +0 -1
  248. package/src/application/host-mcp-owner.ts +0 -41
  249. package/src/application/host-mcp-resolvers.ts +0 -84
  250. package/src/domain/connection-authority.ts +0 -329
  251. package/src/domain/environments.ts +0 -120
  252. package/src/domain/host-mcp-authority-source-admission.ts +0 -25
  253. package/src/domain/host-mcp-task-admission.ts +0 -121
  254. package/src/domain/packs.ts +0 -699
  255. package/src/domain/product-integration-pack.ts +0 -42
  256. package/src/domain/product-integration-skill.gen.ts +0 -52
  257. package/src/remote-mcp-credentials.ts +0 -361
@@ -1,19 +1,29 @@
1
1
  import { modelAllowedByConnections, type ConnectionModelRestrictions } from "@opengeni/db";
2
2
  import {
3
+ withDirectModelProviders,
3
4
  applyModelCatalogDocument,
5
+ CLAUDE_CONNECTION_KINDS,
6
+ claudeProviderId,
7
+ withClaudeConnectionCatalog,
8
+ type ClaudeConnectionCatalog,
4
9
  configuredGatewayWorkspaceProductModelIds,
5
10
  configuredGatewayOrganizationProductModelIds,
6
11
  configuredModels,
12
+ isModelAvailableForNewSelection,
7
13
  configuredModelNotes,
8
14
  configuredOpenRouterWorkspaceProductModelIds,
9
15
  configuredOpenRouterOrganizationProductModelIds,
16
+ configuredOpperWorkspaceProductModelIds,
17
+ configuredOpperOrganizationProductModelIds,
10
18
  configuredProviders,
11
19
  validateModelCatalogSettings,
12
20
  withCodexCatalogProvider,
13
21
  withOrganizationGatewayCatalogProvider,
14
22
  withOrganizationOpenRouterCatalogProvider,
23
+ withOrganizationOpperCatalogProvider,
15
24
  withWorkspaceGatewayCatalogProvider,
16
25
  withWorkspaceOpenRouterCatalogProvider,
26
+ withWorkspaceOpperCatalogProvider,
17
27
  withXaiSubscriptionCatalogProvider,
18
28
  WORKSPACE_GATEWAY_MODEL_ID_PREFIX,
19
29
  WORKSPACE_OPENROUTER_MODEL_ID_PREFIX,
@@ -21,6 +31,10 @@ import {
21
31
  ORGANIZATION_OPENROUTER_PROVIDER_ID,
22
32
  ORGANIZATION_GATEWAY_MODEL_ID_PREFIX,
23
33
  ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX,
34
+ WORKSPACE_OPPER_MODEL_ID_PREFIX,
35
+ WORKSPACE_OPPER_PROVIDER_ID,
36
+ ORGANIZATION_OPPER_MODEL_ID_PREFIX,
37
+ ORGANIZATION_OPPER_PROVIDER_ID,
24
38
  type ConfiguredModel,
25
39
  type Settings,
26
40
  } from "@opengeni/config";
@@ -31,17 +45,21 @@ import {
31
45
  type WorkspaceModelPolicyContract,
32
46
  } from "@opengeni/contracts";
33
47
  import {
48
+ listConnectionsMetadata,
34
49
  getDeploymentModelCatalog,
50
+ listWorkspaceProviderCustomModelsByKind,
51
+ getWorkspaceProviderCustomModelForExecution,
52
+ lockActiveWorkspaceProviderCustomModelForAdmission,
35
53
  getWorkspaceGatewayCustomModelForExecution,
36
54
  getWorkspaceOpenRouterCustomModelForExecution,
37
55
  getOrganizationModelProviderCustomModelForExecution,
38
- listWorkspaceGatewayCustomModels,
39
- listWorkspaceOpenRouterCustomModels,
40
- listOrganizationModelProviderCustomModelsForWorkspace,
56
+ getOrganizationModelProviderCatalogForWorkspace,
41
57
  lockActiveOrganizationModelProviderCustomModelForAdmission,
42
- lockActiveWorkspaceGatewayCustomModelForAdmission,
43
- lockActiveWorkspaceOpenRouterCustomModelForAdmission,
44
58
  type Database,
59
+ type OrganizationClaudeModelAdmissionAuthority,
60
+ type WorkspaceCustomModelProviderKind,
61
+ type WorkspaceGatewayCustomModel,
62
+ type WorkspaceOpenRouterCustomModel,
45
63
  } from "@opengeni/db";
46
64
 
47
65
  export type ResolvedCatalogSettings = {
@@ -70,9 +88,16 @@ export function isWorkspaceOpenRouterCustomModelId(settings: Settings, modelId:
70
88
  );
71
89
  }
72
90
 
91
+ export function isWorkspaceOpperCustomModelId(settings: Settings, modelId: string): boolean {
92
+ return (
93
+ modelId.startsWith(WORKSPACE_OPPER_MODEL_ID_PREFIX) &&
94
+ !configuredOpperWorkspaceProductModelIds(settings).includes(modelId)
95
+ );
96
+ }
97
+
73
98
  export type WorkspaceCustomModelReference = {
74
99
  scope: "workspace" | "organization";
75
- providerKind: "vercel_gateway" | "openrouter";
100
+ providerKind: "vercel_gateway" | "openrouter" | "anthropic" | "claude_subscription" | "opper";
76
101
  upstreamModelId: string;
77
102
  };
78
103
 
@@ -80,6 +105,16 @@ export function workspaceCustomModelReference(
80
105
  settings: Settings,
81
106
  modelId: string,
82
107
  ): WorkspaceCustomModelReference | null {
108
+ for (const scope of ["workspace", "organization"] as const)
109
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
110
+ const prefix = claudeProviderId(kind, scope) + "/";
111
+ if (modelId.startsWith(prefix))
112
+ return {
113
+ scope,
114
+ providerKind: kind,
115
+ upstreamModelId: modelId.slice(prefix.length),
116
+ };
117
+ }
83
118
  if (isWorkspaceGatewayCustomModelId(settings, modelId)) {
84
119
  return {
85
120
  scope: "workspace",
@@ -94,6 +129,23 @@ export function workspaceCustomModelReference(
94
129
  upstreamModelId: modelId.slice(WORKSPACE_OPENROUTER_MODEL_ID_PREFIX.length),
95
130
  };
96
131
  }
132
+ if (isWorkspaceOpperCustomModelId(settings, modelId)) {
133
+ return {
134
+ scope: "workspace",
135
+ providerKind: "opper",
136
+ upstreamModelId: modelId.slice(WORKSPACE_OPPER_MODEL_ID_PREFIX.length),
137
+ };
138
+ }
139
+ if (
140
+ modelId.startsWith(ORGANIZATION_OPPER_MODEL_ID_PREFIX) &&
141
+ !configuredOpperOrganizationProductModelIds(settings).includes(modelId)
142
+ ) {
143
+ return {
144
+ scope: "organization",
145
+ providerKind: "opper",
146
+ upstreamModelId: modelId.slice(ORGANIZATION_OPPER_MODEL_ID_PREFIX.length),
147
+ };
148
+ }
97
149
  if (
98
150
  modelId.startsWith(ORGANIZATION_GATEWAY_MODEL_ID_PREFIX) &&
99
151
  !configuredGatewayOrganizationProductModelIds(settings).includes(modelId)
@@ -127,6 +179,12 @@ export async function lockActiveCustomModelForAdmission(
127
179
  accountId: string;
128
180
  workspaceId: string;
129
181
  reference: WorkspaceCustomModelReference;
182
+ /**
183
+ * The accepted Claude authority this admission re-reads. Every caller
184
+ * states it: an organization Claude model is never admitted without it,
185
+ * so an omitted value would refuse that model silently.
186
+ */
187
+ claudeAuthority: OrganizationClaudeModelAdmissionAuthority | null;
130
188
  },
131
189
  ): Promise<boolean> {
132
190
  if (input.reference.scope === "organization") {
@@ -135,22 +193,18 @@ export async function lockActiveCustomModelForAdmission(
135
193
  accountId: input.accountId,
136
194
  workspaceId: input.workspaceId,
137
195
  providerKind: input.reference.providerKind,
196
+ ...(input.claudeAuthority ? { claudeAuthority: input.claudeAuthority } : {}),
138
197
  upstreamModelId: input.reference.upstreamModelId,
139
198
  }),
140
199
  );
141
200
  }
142
201
  return Boolean(
143
- input.reference.providerKind === "openrouter"
144
- ? await lockActiveWorkspaceOpenRouterCustomModelForAdmission(db, {
145
- accountId: input.accountId,
146
- workspaceId: input.workspaceId,
147
- upstreamModelId: input.reference.upstreamModelId,
148
- })
149
- : await lockActiveWorkspaceGatewayCustomModelForAdmission(db, {
150
- accountId: input.accountId,
151
- workspaceId: input.workspaceId,
152
- upstreamModelId: input.reference.upstreamModelId,
153
- }),
202
+ await lockActiveWorkspaceProviderCustomModelForAdmission(db, {
203
+ accountId: input.accountId,
204
+ workspaceId: input.workspaceId,
205
+ providerKind: input.reference.providerKind,
206
+ upstreamModelId: input.reference.upstreamModelId,
207
+ }),
154
208
  );
155
209
  }
156
210
 
@@ -233,25 +287,48 @@ export async function resolveWorkspaceCatalogSettings(
233
287
  ? [productModelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length)]
234
288
  : [],
235
289
  );
290
+ const retainedOpperUpstreamModelIds = retainedProductModelIds.flatMap((productModelId) =>
291
+ productModelId?.startsWith(WORKSPACE_OPPER_MODEL_ID_PREFIX)
292
+ ? [productModelId.slice(WORKSPACE_OPPER_MODEL_ID_PREFIX.length)]
293
+ : [],
294
+ );
295
+ const retainedOrganizationOpperUpstreamModelIds = retainedProductModelIds.flatMap(
296
+ (productModelId) =>
297
+ productModelId?.startsWith(ORGANIZATION_OPPER_MODEL_ID_PREFIX)
298
+ ? [productModelId.slice(ORGANIZATION_OPPER_MODEL_ID_PREFIX.length)]
299
+ : [],
300
+ );
301
+ // One scoped read per catalog family instead of one scoped transaction per
302
+ // provider kind. The batched readers keep each provider's filters, bound and
303
+ // overflow error. The Claude subscription flag is env-only (the catalog
304
+ // document never changes it), so it decides the requested kinds up front.
305
+ const workspaceCustomModelKinds: WorkspaceCustomModelProviderKind[] = [
306
+ "vercel_gateway",
307
+ "openrouter",
308
+ "opper",
309
+ ...(supportsOrganizationProviderReads
310
+ ? CLAUDE_CONNECTION_KINDS.filter(
311
+ (kind) => kind !== "claude_subscription" || envSettings.claudeSubscriptionEnabled,
312
+ )
313
+ : []),
314
+ ];
236
315
  const [
237
316
  resolved,
238
- activeGatewayCustomModels,
239
- activeOpenRouterCustomModels,
317
+ workspaceCustomModels,
240
318
  retainedGatewayCustomModels,
241
319
  retainedOpenRouterCustomModels,
242
- organizationGatewayCustomModels,
243
- organizationOpenRouterCustomModels,
320
+ organizationProviders,
244
321
  retainedOrganizationGatewayCustomModels,
245
322
  retainedOrganizationOpenRouterCustomModels,
323
+ directConnections,
324
+ retainedOpperCustomModels,
325
+ retainedOrganizationOpperCustomModels,
246
326
  ] = await Promise.all([
247
327
  resolveCatalogSettings(db, envSettings),
248
- listWorkspaceGatewayCustomModels(db, {
249
- accountId: input.accountId,
250
- workspaceId: input.workspaceId,
251
- }),
252
- listWorkspaceOpenRouterCustomModels(db, {
328
+ listWorkspaceProviderCustomModelsByKind(db, {
253
329
  accountId: input.accountId,
254
330
  workspaceId: input.workspaceId,
331
+ providerKinds: workspaceCustomModelKinds,
255
332
  }),
256
333
  Promise.all(
257
334
  [...new Set(retainedGatewayUpstreamModelIds)].map(
@@ -274,19 +351,12 @@ export async function resolveWorkspaceCatalogSettings(
274
351
  ),
275
352
  ),
276
353
  supportsOrganizationProviderReads
277
- ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
354
+ ? getOrganizationModelProviderCatalogForWorkspace(db, {
278
355
  accountId: input.accountId,
279
356
  workspaceId: input.workspaceId,
280
- providerKind: "vercel_gateway",
357
+ providerKinds: ["vercel_gateway", "openrouter", "opper", ...CLAUDE_CONNECTION_KINDS],
281
358
  })
282
- : Promise.resolve([]),
283
- supportsOrganizationProviderReads
284
- ? listOrganizationModelProviderCustomModelsForWorkspace(db, {
285
- accountId: input.accountId,
286
- workspaceId: input.workspaceId,
287
- providerKind: "openrouter",
288
- })
289
- : Promise.resolve([]),
359
+ : null,
290
360
  supportsOrganizationProviderReads
291
361
  ? Promise.all(
292
362
  [...new Set(retainedOrganizationGatewayUpstreamModelIds)].map(
@@ -313,6 +383,33 @@ export async function resolveWorkspaceCatalogSettings(
313
383
  ),
314
384
  )
315
385
  : Promise.resolve([]),
386
+ supportsOrganizationProviderReads
387
+ ? listConnectionsMetadata(db, input.workspaceId, null)
388
+ : Promise.resolve([]),
389
+ Promise.all(
390
+ [...new Set(retainedOpperUpstreamModelIds)].map(
391
+ async (upstreamModelId) =>
392
+ await getWorkspaceProviderCustomModelForExecution(db, {
393
+ accountId: input.accountId,
394
+ workspaceId: input.workspaceId,
395
+ providerKind: "opper",
396
+ upstreamModelId,
397
+ }),
398
+ ),
399
+ ),
400
+ supportsOrganizationProviderReads
401
+ ? Promise.all(
402
+ [...new Set(retainedOrganizationOpperUpstreamModelIds)].map(
403
+ async (upstreamModelId) =>
404
+ await getOrganizationModelProviderCustomModelForExecution(db, {
405
+ accountId: input.accountId,
406
+ workspaceId: input.workspaceId,
407
+ providerKind: "opper",
408
+ upstreamModelId,
409
+ }),
410
+ ),
411
+ )
412
+ : Promise.resolve([]),
316
413
  ]);
317
414
  const includeRetainedModels = <T extends { upstreamModelId: string }>(
318
415
  activeModels: readonly T[],
@@ -332,32 +429,91 @@ export async function resolveWorkspaceCatalogSettings(
332
429
  return customModels;
333
430
  };
334
431
  const gatewayCustomModels = includeRetainedModels(
335
- activeGatewayCustomModels,
432
+ workspaceCustomModels.vercel_gateway as WorkspaceGatewayCustomModel[],
336
433
  retainedGatewayCustomModels,
337
434
  );
338
435
  const openRouterCustomModels = includeRetainedModels(
339
- activeOpenRouterCustomModels,
436
+ workspaceCustomModels.openrouter as WorkspaceOpenRouterCustomModel[],
340
437
  retainedOpenRouterCustomModels,
341
438
  );
342
439
  const organizationGatewayModels = includeRetainedModels(
343
- organizationGatewayCustomModels,
440
+ organizationProviders?.vercel_gateway.models ?? [],
344
441
  retainedOrganizationGatewayCustomModels,
345
442
  );
346
443
  const organizationOpenRouterModels = includeRetainedModels(
347
- organizationOpenRouterCustomModels,
444
+ organizationProviders?.openrouter.models ?? [],
348
445
  retainedOrganizationOpenRouterCustomModels,
349
446
  );
447
+ const opperCustomModels = includeRetainedModels(
448
+ workspaceCustomModels.opper ?? [],
449
+ retainedOpperCustomModels,
450
+ );
451
+ const organizationOpperModels = includeRetainedModels(
452
+ organizationProviders?.opper.models ?? [],
453
+ retainedOrganizationOpperCustomModels,
454
+ );
455
+ const claudeConnections: ClaudeConnectionCatalog = {};
456
+ if (organizationProviders)
457
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
458
+ const models = [...organizationProviders[kind].models];
459
+ const prefix = claudeProviderId(kind) + "/";
460
+ for (const productId of retainedProductModelIds)
461
+ if (productId?.startsWith(prefix)) {
462
+ const retained = await getOrganizationModelProviderCustomModelForExecution(db, {
463
+ accountId: input.accountId,
464
+ workspaceId: input.workspaceId,
465
+ providerKind: kind,
466
+ upstreamModelId: productId.slice(prefix.length),
467
+ });
468
+ if (retained && !models.some((model) => model.id === retained.id)) models.push(retained);
469
+ }
470
+ claudeConnections[kind] = { models };
471
+ }
472
+ const workspaceClaudeConnections: ClaudeConnectionCatalog = {};
473
+ if (supportsOrganizationProviderReads)
474
+ for (const kind of CLAUDE_CONNECTION_KINDS) {
475
+ if (kind === "claude_subscription" && !resolved.settings.claudeSubscriptionEnabled) continue;
476
+ const models = [...workspaceCustomModels[kind]];
477
+ const prefix = claudeProviderId(kind, "workspace") + "/";
478
+ for (const productId of retainedProductModelIds) {
479
+ if (!productId?.startsWith(prefix)) continue;
480
+ const retained = await getWorkspaceProviderCustomModelForExecution(db, {
481
+ accountId: input.accountId,
482
+ workspaceId: input.workspaceId,
483
+ providerKind: kind,
484
+ upstreamModelId: productId.slice(prefix.length),
485
+ });
486
+ if (retained && !models.some((model) => model.id === retained.id)) models.push(retained);
487
+ }
488
+ workspaceClaudeConnections[kind] = { models };
489
+ }
350
490
  return {
351
491
  ...resolved,
352
- settings: withOrganizationOpenRouterCatalogProvider(
353
- withOrganizationGatewayCatalogProvider(
354
- withWorkspaceOpenRouterCatalogProvider(
355
- withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
356
- openRouterCustomModels,
492
+ settings: withDirectModelProviders(
493
+ withClaudeConnectionCatalog(
494
+ withClaudeConnectionCatalog(
495
+ withOrganizationOpperCatalogProvider(
496
+ withWorkspaceOpperCatalogProvider(
497
+ withOrganizationOpenRouterCatalogProvider(
498
+ withOrganizationGatewayCatalogProvider(
499
+ withWorkspaceOpenRouterCatalogProvider(
500
+ withWorkspaceGatewayCatalogProvider(resolved.settings, gatewayCustomModels),
501
+ openRouterCustomModels,
502
+ ),
503
+ organizationGatewayModels,
504
+ ),
505
+ organizationOpenRouterModels,
506
+ ),
507
+ opperCustomModels,
508
+ ),
509
+ organizationOpperModels,
510
+ ),
511
+ claudeConnections,
357
512
  ),
358
- organizationGatewayModels,
513
+ workspaceClaudeConnections,
514
+ "workspace",
359
515
  ),
360
- organizationOpenRouterModels,
516
+ directConnections,
361
517
  ),
362
518
  };
363
519
  }
@@ -387,8 +543,12 @@ export type WorkspaceModelSelectionInput = {
387
543
  xaiSubscriptionActive?: boolean;
388
544
  workspaceGatewayConnectionActive?: boolean;
389
545
  workspaceOpenRouterConnectionActive?: boolean;
546
+ workspaceOpperConnectionActive?: boolean;
547
+ claudeConnections?: ClaudeConnectionCatalog;
548
+ workspaceClaudeConnections?: ClaudeConnectionCatalog;
390
549
  organizationGatewayConnectionActive?: boolean;
391
550
  organizationOpenRouterConnectionActive?: boolean;
551
+ organizationOpperConnectionActive?: boolean;
392
552
  workspaceGatewayCustomModels?: readonly {
393
553
  upstreamModelId: string;
394
554
  label?: string | null;
@@ -405,6 +565,14 @@ export type WorkspaceModelSelectionInput = {
405
565
  upstreamModelId: string;
406
566
  label?: string | null;
407
567
  }[];
568
+ workspaceOpperCustomModels?: readonly {
569
+ upstreamModelId: string;
570
+ label?: string | null;
571
+ }[];
572
+ organizationOpperCustomModels?: readonly {
573
+ upstreamModelId: string;
574
+ label?: string | null;
575
+ }[];
408
576
  credentialReadinessObservations?:
409
577
  | Readonly<Record<string, ModelCredentialReadinessObservation>>
410
578
  | undefined;
@@ -428,6 +596,23 @@ function modelDefinitionRunnable(model: ConfiguredModel): boolean {
428
596
  );
429
597
  }
430
598
 
599
+ /**
600
+ * Stable fresh-selection admission, independent of transient observations.
601
+ * Missing catalog membership is handled by the caller's lookup. Resolver-based
602
+ * deployment credentials (Azure AD/managed identity) are resolved at execution;
603
+ * absent, stale or failed observations cannot make their definition uncreatable.
604
+ */
605
+ export function isWorkspaceModelAdmissible(selection: WorkspaceModelSelection): boolean {
606
+ if (!modelDefinitionRunnable(selection.model) || !selection.policyAllowed) return false;
607
+ const source = selection.model.credentialSource;
608
+ if (source.kind === "deployment") {
609
+ return source.mechanism !== "api_key" || selection.credentialReadiness.status === "ready";
610
+ }
611
+ // Connected subscriptions and workspace/organization connections have a
612
+ // durable active/reauth prerequisite, unlike deployment credential resolvers.
613
+ return selection.credentialReadiness.status === "ready";
614
+ }
615
+
431
616
  function observedCredentialReadiness(input: {
432
617
  observation: ModelCredentialReadinessObservation | undefined;
433
618
  basis: "connection" | "resolver";
@@ -487,8 +672,12 @@ function credentialReadinessFor(input: {
487
672
  xaiSubscriptionActive: boolean;
488
673
  workspaceGatewayConnectionActive: boolean;
489
674
  workspaceOpenRouterConnectionActive: boolean;
675
+ workspaceOpperConnectionActive: boolean;
676
+ claudeConnections?: ClaudeConnectionCatalog | undefined;
677
+ workspaceClaudeConnections?: ClaudeConnectionCatalog | undefined;
490
678
  organizationGatewayConnectionActive: boolean;
491
679
  organizationOpenRouterConnectionActive: boolean;
680
+ organizationOpperConnectionActive: boolean;
492
681
  observation: ModelCredentialReadinessObservation | undefined;
493
682
  nowMs: number;
494
683
  maxAgeMs: number;
@@ -496,7 +685,14 @@ function credentialReadinessFor(input: {
496
685
  const source = input.model.credentialSource;
497
686
  if (source.kind === "connected_subscription") {
498
687
  const active =
499
- source.provider === "xai" ? input.xaiSubscriptionActive : input.codexSubscriptionActive;
688
+ source.provider === "xai"
689
+ ? input.xaiSubscriptionActive
690
+ : source.provider === "claude"
691
+ ? (input.model.providerId === claudeProviderId("claude_subscription", "workspace")
692
+ ? input.workspaceClaudeConnections
693
+ : input.claudeConnections
694
+ )?.claude_subscription?.active === true
695
+ : input.codexSubscriptionActive;
500
696
  return active
501
697
  ? { status: "ready", reason: null, basis: "connection", checkedAt: null }
502
698
  : {
@@ -507,10 +703,20 @@ function credentialReadinessFor(input: {
507
703
  };
508
704
  }
509
705
  if (source.kind === "workspace_connection") {
706
+ const claudeKind = CLAUDE_CONNECTION_KINDS.find(
707
+ (kind) => claudeProviderId(kind, "workspace") === input.model.providerId,
708
+ );
510
709
  const connectionActive =
511
- input.model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID
512
- ? input.workspaceOpenRouterConnectionActive
513
- : input.workspaceGatewayConnectionActive;
710
+ input.provider?.kind === "direct-openai-workspace" ||
711
+ input.provider?.kind === "direct-azure-workspace"
712
+ ? true
713
+ : claudeKind
714
+ ? input.workspaceClaudeConnections?.[claudeKind]?.active === true
715
+ : input.model.providerId === WORKSPACE_OPENROUTER_PROVIDER_ID
716
+ ? input.workspaceOpenRouterConnectionActive
717
+ : input.model.providerId === WORKSPACE_OPPER_PROVIDER_ID
718
+ ? input.workspaceOpperConnectionActive
719
+ : input.workspaceGatewayConnectionActive;
514
720
  return connectionActive
515
721
  ? { status: "ready", reason: null, basis: "connection", checkedAt: null }
516
722
  : {
@@ -521,10 +727,16 @@ function credentialReadinessFor(input: {
521
727
  };
522
728
  }
523
729
  if (source.kind === "organization_connection") {
524
- const connectionActive =
525
- input.model.providerId === ORGANIZATION_OPENROUTER_PROVIDER_ID
730
+ const claudeKind = CLAUDE_CONNECTION_KINDS.find(
731
+ (kind) => claudeProviderId(kind) === input.model.providerId,
732
+ );
733
+ const connectionActive = claudeKind
734
+ ? input.claudeConnections?.[claudeKind]?.active === true
735
+ : input.model.providerId === ORGANIZATION_OPENROUTER_PROVIDER_ID
526
736
  ? input.organizationOpenRouterConnectionActive
527
- : input.organizationGatewayConnectionActive;
737
+ : input.model.providerId === ORGANIZATION_OPPER_PROVIDER_ID
738
+ ? input.organizationOpperConnectionActive
739
+ : input.organizationGatewayConnectionActive;
528
740
  return connectionActive
529
741
  ? { status: "ready", reason: null, basis: "connection", checkedAt: null }
530
742
  : {
@@ -610,6 +822,8 @@ function availabilityFor(input: {
610
822
  model: ConfiguredModel;
611
823
  credentialReadiness: ModelCredentialReadinessV1;
612
824
  policyAllowed: boolean;
825
+ /** Blocked only because the workspace turned Opengeni credits off. */
826
+ creditsDisabled?: boolean;
613
827
  observation?: ModelAvailabilityObservation | undefined;
614
828
  nowMs: number;
615
829
  maxAgeMs: number;
@@ -639,7 +853,7 @@ function availabilityFor(input: {
639
853
  return {
640
854
  status: "unavailable",
641
855
  selectable: false,
642
- reason: "policy_blocked",
856
+ reason: input.creditsDisabled ? "credits_disabled" : "policy_blocked",
643
857
  checkedAt: null,
644
858
  };
645
859
  }
@@ -683,15 +897,31 @@ export function resolveWorkspaceModelSelection(
683
897
  const xaiSettings = input.settings.supergrokSubscriptionEnabled
684
898
  ? withXaiSubscriptionCatalogProvider(codexSettings)
685
899
  : codexSettings;
686
- const catalogSettings = withOrganizationOpenRouterCatalogProvider(
687
- withOrganizationGatewayCatalogProvider(
688
- withWorkspaceOpenRouterCatalogProvider(
689
- withWorkspaceGatewayCatalogProvider(xaiSettings, input.workspaceGatewayCustomModels ?? []),
690
- input.workspaceOpenRouterCustomModels ?? [],
900
+ const catalogSettings = withClaudeConnectionCatalog(
901
+ withClaudeConnectionCatalog(
902
+ withOrganizationOpperCatalogProvider(
903
+ withWorkspaceOpperCatalogProvider(
904
+ withOrganizationOpenRouterCatalogProvider(
905
+ withOrganizationGatewayCatalogProvider(
906
+ withWorkspaceOpenRouterCatalogProvider(
907
+ withWorkspaceGatewayCatalogProvider(
908
+ xaiSettings,
909
+ input.workspaceGatewayCustomModels ?? [],
910
+ ),
911
+ input.workspaceOpenRouterCustomModels ?? [],
912
+ ),
913
+ input.organizationGatewayCustomModels ?? [],
914
+ ),
915
+ input.organizationOpenRouterCustomModels ?? [],
916
+ ),
917
+ input.workspaceOpperCustomModels ?? [],
918
+ ),
919
+ input.organizationOpperCustomModels ?? [],
691
920
  ),
692
- input.organizationGatewayCustomModels ?? [],
921
+ input.claudeConnections ?? {},
693
922
  ),
694
- input.organizationOpenRouterCustomModels ?? [],
923
+ input.workspaceClaudeConnections ?? {},
924
+ "workspace",
695
925
  );
696
926
  const providers = new Map(
697
927
  configuredProviders(catalogSettings).map((provider) => [provider.id, provider]),
@@ -708,38 +938,62 @@ export function resolveWorkspaceModelSelection(
708
938
  ? input.credentialReadinessMaxAgeMs
709
939
  : MODEL_CREDENTIAL_READINESS_OBSERVATION_MAX_AGE_MS;
710
940
 
711
- return configuredModels(catalogSettings).map((model) => {
712
- const provider = providers.get(model.providerId);
713
- const policyAllowed =
714
- evaluateWorkspaceModelPolicy(input.policy, {
941
+ return configuredModels(catalogSettings)
942
+ .filter((model) => isModelAvailableForNewSelection(catalogSettings, model.id))
943
+ .map((model) => {
944
+ const provider = providers.get(model.providerId);
945
+ const verdict = evaluateWorkspaceModelPolicy(input.policy, {
715
946
  providerId: model.providerId,
716
947
  modelId: model.id,
717
- }).allowed && modelAllowedByConnections(input.connectionModelRestrictions ?? {}, model.id);
718
- const credentialReadiness = credentialReadinessFor({
719
- model,
720
- provider,
721
- codexSubscriptionActive: input.codexSubscriptionActive,
722
- xaiSubscriptionActive: input.xaiSubscriptionActive === true,
723
- workspaceGatewayConnectionActive: input.workspaceGatewayConnectionActive === true,
724
- workspaceOpenRouterConnectionActive: input.workspaceOpenRouterConnectionActive === true,
725
- organizationGatewayConnectionActive: input.organizationGatewayConnectionActive === true,
726
- organizationOpenRouterConnectionActive: input.organizationOpenRouterConnectionActive === true,
727
- observation: input.credentialReadinessObservations?.[model.definitionVersion],
728
- nowMs,
729
- maxAgeMs,
730
- });
731
- return {
732
- model,
733
- credentialReadiness,
734
- policyAllowed,
735
- availability: availabilityFor({
948
+ chargesCredits: model.cost === "credits",
949
+ });
950
+ const allowedByConnections = modelAllowedByConnections(
951
+ input.connectionModelRestrictions ?? {},
952
+ model.id,
953
+ );
954
+ const policyAllowed = verdict.allowed && allowedByConnections;
955
+ // Name the credit switch only when it is the sole reason; an allowlist
956
+ // or connection restriction keeps the generic policy reason.
957
+ const creditsDisabled =
958
+ !verdict.allowed &&
959
+ verdict.reason === "credits" &&
960
+ allowedByConnections &&
961
+ input.policy != null &&
962
+ evaluateWorkspaceModelPolicy(
963
+ { ...input.policy, allowCreditModels: true },
964
+ { providerId: model.providerId, modelId: model.id, chargesCredits: true },
965
+ ).allowed;
966
+ const credentialReadiness = credentialReadinessFor({
736
967
  model,
737
- credentialReadiness,
738
- policyAllowed,
739
- observation: input.observations?.[model.definitionVersion],
968
+ provider,
969
+ codexSubscriptionActive: input.codexSubscriptionActive,
970
+ xaiSubscriptionActive: input.xaiSubscriptionActive === true,
971
+ workspaceGatewayConnectionActive: input.workspaceGatewayConnectionActive === true,
972
+ workspaceOpenRouterConnectionActive: input.workspaceOpenRouterConnectionActive === true,
973
+ workspaceOpperConnectionActive: input.workspaceOpperConnectionActive === true,
974
+ claudeConnections: input.claudeConnections,
975
+ workspaceClaudeConnections: input.workspaceClaudeConnections,
976
+ organizationGatewayConnectionActive: input.organizationGatewayConnectionActive === true,
977
+ organizationOpenRouterConnectionActive:
978
+ input.organizationOpenRouterConnectionActive === true,
979
+ organizationOpperConnectionActive: input.organizationOpperConnectionActive === true,
980
+ observation: input.credentialReadinessObservations?.[model.definitionVersion],
740
981
  nowMs,
741
982
  maxAgeMs,
742
- }),
743
- };
744
- });
983
+ });
984
+ return {
985
+ model,
986
+ credentialReadiness,
987
+ policyAllowed,
988
+ availability: availabilityFor({
989
+ model,
990
+ credentialReadiness,
991
+ policyAllowed,
992
+ creditsDisabled,
993
+ observation: input.observations?.[model.definitionVersion],
994
+ nowMs,
995
+ maxAgeMs,
996
+ }),
997
+ };
998
+ });
745
999
  }