@opengeni/core 1.4.0 → 1.4.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/application/modal-native-live-declaration-join.d.ts +37 -0
- package/dist/application/modal-native-original-configuration.d.ts +79 -0
- package/dist/domain/capabilities.d.ts +1 -0
- package/dist/domain/knowledge-messages.d.ts +4 -4
- package/dist/domain/realtime-voice-billing.d.ts +21 -7
- package/dist/domain/session-start-timing.d.ts +1 -1
- package/dist/domain/session-tool-policy.d.ts +7 -3
- package/dist/domain/voice-input-billing.d.ts +3 -2
- package/dist/goal-admission.d.ts +2 -0
- package/dist/index.js +575 -523
- package/dist/index.js.map +1 -1
- package/package.json +12 -12
- package/src/application/modal-native-live-declaration-join.ts +196 -0
- package/src/application/modal-native-original-configuration.ts +261 -0
- package/src/default-session-model.ts +58 -88
- package/src/domain/capabilities.ts +44 -2
- package/src/domain/realtime-voice-billing.ts +80 -10
- package/src/domain/session-start-timing.ts +5 -0
- package/src/domain/session-tool-policy.ts +6 -2
- package/src/domain/sessions.ts +130 -96
- package/src/domain/voice-input-billing.ts +9 -4
- package/src/model-catalog.ts +32 -38
package/src/domain/sessions.ts
CHANGED
|
@@ -2052,6 +2052,11 @@ export async function postUserMessageTurn(
|
|
|
2052
2052
|
: {}),
|
|
2053
2053
|
controlLockTimeoutMs: workspaceControlRequestLockTimeoutMs(),
|
|
2054
2054
|
}),
|
|
2055
|
+
undefined,
|
|
2056
|
+
"shared",
|
|
2057
|
+
// Linked-actor capture reauthorizes under the organization-membership
|
|
2058
|
+
// lock; take it before the tenancy fence and the canonical prefix.
|
|
2059
|
+
Boolean(input.captureTurnAuthority),
|
|
2055
2060
|
),
|
|
2056
2061
|
);
|
|
2057
2062
|
} catch (error) {
|
|
@@ -2590,27 +2595,35 @@ async function createSessionForRequestInFileScope(
|
|
|
2590
2595
|
payload.idempotencyKey &&
|
|
2591
2596
|
(effectiveVisibility !== "user_private" || replayManagedHumanSubjectId !== null)
|
|
2592
2597
|
) {
|
|
2598
|
+
const createIdempotencyKey = payload.idempotencyKey;
|
|
2593
2599
|
try {
|
|
2594
|
-
const initializedReplay = await
|
|
2595
|
-
|
|
2596
|
-
|
|
2597
|
-
|
|
2598
|
-
|
|
2599
|
-
|
|
2600
|
-
|
|
2601
|
-
|
|
2602
|
-
|
|
2603
|
-
|
|
2604
|
-
|
|
2605
|
-
|
|
2600
|
+
const initializedReplay = await measureSessionStartPhase(
|
|
2601
|
+
unresolvedDeps.observability,
|
|
2602
|
+
"idempotent_replay",
|
|
2603
|
+
() =>
|
|
2604
|
+
getInitializedSessionCreateReplay(db, {
|
|
2605
|
+
bundledSkillIds,
|
|
2606
|
+
accountId: grant.accountId,
|
|
2607
|
+
workspaceId,
|
|
2608
|
+
subjectId: replayManagedHumanSubjectId ?? grant.subjectId,
|
|
2609
|
+
...(replayManagedHumanSubjectId
|
|
2610
|
+
? { activeManagedHumanSubjectId: replayManagedHumanSubjectId }
|
|
2611
|
+
: {}),
|
|
2612
|
+
createIdempotencyKey,
|
|
2613
|
+
selectedInstalledSkillIds: payload.installedSkillIds ?? [],
|
|
2614
|
+
initialAgentLearning: payload.agentLearning,
|
|
2615
|
+
...sessionScope,
|
|
2606
2616
|
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2617
|
+
...(payload.requestedSessionId
|
|
2618
|
+
? { requestedSessionId: payload.requestedSessionId }
|
|
2619
|
+
: {}),
|
|
2620
|
+
visibility: effectiveVisibility,
|
|
2621
|
+
variableSetIds: payload.variableSetIds ?? [],
|
|
2622
|
+
initialPersonalResourceAttachmentIntent: payload.personalResourceAttachment ?? null,
|
|
2623
|
+
deferInitialTurn: payload.startMode === "realtime",
|
|
2624
|
+
agentConfig,
|
|
2625
|
+
}),
|
|
2626
|
+
);
|
|
2614
2627
|
if (initializedReplay) {
|
|
2615
2628
|
if (initializedReplay.outcome === "denied") {
|
|
2616
2629
|
throw new SessionSpawnDeniedError(SessionSpawnDenial.parse(initializedReplay.denial));
|
|
@@ -2988,36 +3001,43 @@ async function createSessionForRequestInFileScope(
|
|
|
2988
3001
|
const tools = withFirstPartyTools(selectedTools, runtimeSettings);
|
|
2989
3002
|
|
|
2990
3003
|
const captureLinkedAuthority = prepareExternalLinkTurnAdmission(authorization);
|
|
2991
|
-
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
|
|
3004
|
+
const { attachmentOwnerContext, variableSets } = await measureSessionStartPhase(
|
|
3005
|
+
unresolvedDeps.observability,
|
|
3006
|
+
"resource_validation",
|
|
3007
|
+
async () => {
|
|
3008
|
+
await validateGitHubRepositorySelection(db, workspaceId, resources);
|
|
3009
|
+
if (resources.some((resource) => resource.kind === "file") && !objectStorage) {
|
|
3010
|
+
throw new HTTPException(503, {
|
|
3011
|
+
message: "object storage is not configured",
|
|
3012
|
+
});
|
|
3013
|
+
}
|
|
3014
|
+
const ownerContext = authorization
|
|
3015
|
+
? await fileOwnerContextForAccess({ db }, authorization, "sessions:create")
|
|
3016
|
+
: grant.principalKind === "agent_attempt"
|
|
3017
|
+
? await fileOwnerContextForAgent({ db }, grant, "sessions:create")
|
|
3018
|
+
: undefined;
|
|
3019
|
+
await validateFileResources(
|
|
3020
|
+
db,
|
|
3021
|
+
grant.accountId,
|
|
3022
|
+
workspaceId,
|
|
3023
|
+
personalResourceSubjectId ?? grant.subjectId,
|
|
3024
|
+
resources,
|
|
3025
|
+
ownerContext,
|
|
3026
|
+
);
|
|
3027
|
+
// Every selected Variable Set is independently authorized. Scope does not
|
|
3028
|
+
// affect precedence: explicit order is low-to-high and later sets win name
|
|
3029
|
+
// collisions.
|
|
3030
|
+
const validatedVariableSets: VariableSet[] = [];
|
|
3031
|
+
for (const variableSetId of payload.variableSetIds ?? []) {
|
|
3032
|
+
validatedVariableSets.push(
|
|
3033
|
+
await validateVariableSetAttachment({ settings, db }, grant, workspaceId, variableSetId),
|
|
3034
|
+
);
|
|
3035
|
+
}
|
|
3036
|
+
return { attachmentOwnerContext: ownerContext, variableSets: validatedVariableSets };
|
|
3037
|
+
},
|
|
3038
|
+
);
|
|
3002
3039
|
const attachmentOwner =
|
|
3003
3040
|
attachmentOwnerContext?.privateFileOwnerSubjectId === grant.subjectId ? grant.subjectId : null;
|
|
3004
|
-
await validateFileResources(
|
|
3005
|
-
db,
|
|
3006
|
-
grant.accountId,
|
|
3007
|
-
workspaceId,
|
|
3008
|
-
personalResourceSubjectId ?? grant.subjectId,
|
|
3009
|
-
resources,
|
|
3010
|
-
attachmentOwnerContext,
|
|
3011
|
-
);
|
|
3012
|
-
// Every selected Variable Set is independently authorized. Scope does not
|
|
3013
|
-
// affect precedence: explicit order is low-to-high and later sets win name
|
|
3014
|
-
// collisions.
|
|
3015
|
-
const variableSets: VariableSet[] = [];
|
|
3016
|
-
for (const variableSetId of payload.variableSetIds ?? []) {
|
|
3017
|
-
variableSets.push(
|
|
3018
|
-
await validateVariableSetAttachment({ settings, db }, grant, workspaceId, variableSetId),
|
|
3019
|
-
);
|
|
3020
|
-
}
|
|
3021
3041
|
// RIG BINDING (M3). Resolve the rig this session rides — a UUID binds that
|
|
3022
3042
|
// rig, null explicitly opts out, and omission inherits the workspace default
|
|
3023
3043
|
// (workspaces.default_rig_id) — then FREEZE both the rig id and its currently-
|
|
@@ -3029,22 +3049,27 @@ async function createSessionForRequestInFileScope(
|
|
|
3029
3049
|
// - A stale workspace-default rig (deleted → FK-nulled, or somehow with no
|
|
3030
3050
|
// active version) degrades SILENTLY to rig-less: an operator-side default
|
|
3031
3051
|
// must never brick every create in the workspace.
|
|
3032
|
-
const
|
|
3033
|
-
|
|
3034
|
-
|
|
3035
|
-
|
|
3036
|
-
|
|
3037
|
-
|
|
3038
|
-
|
|
3039
|
-
|
|
3040
|
-
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3052
|
+
const { frozenRigId, frozenRigVersionId } = await measureSessionStartPhase(
|
|
3053
|
+
unresolvedDeps.observability,
|
|
3054
|
+
"rig_binding",
|
|
3055
|
+
async () => {
|
|
3056
|
+
const requestedRigId =
|
|
3057
|
+
payload.rigId === undefined
|
|
3058
|
+
? await getWorkspaceDefaultRigId(db, workspaceId)
|
|
3059
|
+
: payload.rigId;
|
|
3060
|
+
if (!requestedRigId) return { frozenRigId: null, frozenRigVersionId: null };
|
|
3061
|
+
const rig = await getRig(db, grant, requestedRigId);
|
|
3062
|
+
if (!rig || !rig.activeVersion) {
|
|
3063
|
+
if (payload.rigId) {
|
|
3064
|
+
throw new HTTPException(422, {
|
|
3065
|
+
message: rig
|
|
3066
|
+
? `sandbox environment ${payload.rigId} has no active version to bind`
|
|
3067
|
+
: `unknown rigId: ${payload.rigId}`,
|
|
3068
|
+
});
|
|
3069
|
+
}
|
|
3070
|
+
// else: workspace-default fallback that no longer resolves → rig-less.
|
|
3071
|
+
return { frozenRigId: null, frozenRigVersionId: null };
|
|
3045
3072
|
}
|
|
3046
|
-
// else: workspace-default fallback that no longer resolves → rig-less.
|
|
3047
|
-
} else {
|
|
3048
3073
|
for (const defaultVariableSetId of new Set(rig.activeVersion.defaultVariableSetIds)) {
|
|
3049
3074
|
await validateVariableSetAttachment(
|
|
3050
3075
|
{ settings, db },
|
|
@@ -3053,10 +3078,12 @@ async function createSessionForRequestInFileScope(
|
|
|
3053
3078
|
defaultVariableSetId,
|
|
3054
3079
|
);
|
|
3055
3080
|
}
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
|
|
3081
|
+
return {
|
|
3082
|
+
frozenRigId: rig.id as string | null,
|
|
3083
|
+
frozenRigVersionId: rig.activeVersion.id as string | null,
|
|
3084
|
+
};
|
|
3085
|
+
},
|
|
3086
|
+
);
|
|
3060
3087
|
// CHANNEL FILING. Pure rail organization: a UUID files the session into that
|
|
3061
3088
|
// workspace channel, omission/null leaves it unfiled (inbox). Resolved
|
|
3062
3089
|
// workspace-scoped so a foreign channel id can never attach; an explicit
|
|
@@ -3085,25 +3112,27 @@ async function createSessionForRequestInFileScope(
|
|
|
3085
3112
|
// inherited calling-turn model, or deployment default — so the policy must
|
|
3086
3113
|
// vet that effective value, not just explicit ones (a restricted workspace's
|
|
3087
3114
|
// inherited/default-model session would otherwise be born blocked).
|
|
3088
|
-
await
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3094
|
-
|
|
3095
|
-
|
|
3096
|
-
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
3104
|
-
|
|
3115
|
+
await measureSessionStartPhase(unresolvedDeps.observability, "model_admission", async () => {
|
|
3116
|
+
await assertWorkspaceModelPolicyAllows(db, settings, workspaceId, model);
|
|
3117
|
+
// Direct creation is a fresh model selection. Child inheritance and keyed
|
|
3118
|
+
// repair preserve the existing accepted-model/authority rules.
|
|
3119
|
+
if (retainedKeyedShellModel === null && !parentSession) {
|
|
3120
|
+
const selections = await resolveCallerWorkspaceModelSelections(db, settings, {
|
|
3121
|
+
accountId: grant.accountId,
|
|
3122
|
+
workspaceId,
|
|
3123
|
+
subjectId: personalResourceSubjectId ?? grant.subjectId,
|
|
3124
|
+
...(xaiProviderAccountAuthoritySnapshot
|
|
3125
|
+
? { xaiAuthoritySnapshot: xaiProviderAccountAuthoritySnapshot }
|
|
3126
|
+
: {}),
|
|
3127
|
+
...(claudeProviderAccountAuthoritySnapshot
|
|
3128
|
+
? { claudeAuthoritySnapshot: claudeProviderAccountAuthoritySnapshot }
|
|
3129
|
+
: {}),
|
|
3130
|
+
});
|
|
3131
|
+
if (!admissibleWorkspaceModel(selections, model)) {
|
|
3132
|
+
throw new HTTPException(422, { message: `model is not selectable: ${model}` });
|
|
3133
|
+
}
|
|
3105
3134
|
}
|
|
3106
|
-
}
|
|
3135
|
+
});
|
|
3107
3136
|
const inheritedReasoningEffort =
|
|
3108
3137
|
parentCallingTurn?.reasoningEffort ??
|
|
3109
3138
|
parentSession?.reasoningEffort ??
|
|
@@ -3307,18 +3336,23 @@ async function createSessionForRequestInFileScope(
|
|
|
3307
3336
|
workspace.settings,
|
|
3308
3337
|
),
|
|
3309
3338
|
});
|
|
3310
|
-
const { personalConnectionDelegations, mcpAccountBindings } = await
|
|
3311
|
-
|
|
3312
|
-
|
|
3313
|
-
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
|
|
3317
|
-
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3339
|
+
const { personalConnectionDelegations, mcpAccountBindings } = await measureSessionStartPhase(
|
|
3340
|
+
unresolvedDeps.observability,
|
|
3341
|
+
"connection_freeze",
|
|
3342
|
+
() =>
|
|
3343
|
+
freezeConnectionAccounts({
|
|
3344
|
+
db,
|
|
3345
|
+
accountId: grant.accountId,
|
|
3346
|
+
workspaceId,
|
|
3347
|
+
settings: runtimeSettings,
|
|
3348
|
+
tools: connectionAccountTools,
|
|
3349
|
+
resources,
|
|
3350
|
+
source: connectionDelegationSource,
|
|
3351
|
+
authoritySelections: payload.connectionAccounts,
|
|
3352
|
+
googleDrivePublicationEnabled,
|
|
3353
|
+
atlassianEnabled,
|
|
3354
|
+
}),
|
|
3355
|
+
);
|
|
3322
3356
|
if (effectiveGoal) {
|
|
3323
3357
|
const missingGoalTools = ["goal_update", "goal_progress", "goal_complete", "goal_pause"].filter(
|
|
3324
3358
|
(name) => !firstPartyMcpTools.includes(name as FirstPartyMcpToolName),
|
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
VOICE_TRANSCRIPTION_DEBIT_TYPE,
|
|
19
19
|
VOICE_TRANSCRIPTION_SOURCE_TYPE,
|
|
20
20
|
voiceTranscriptionSettlementKeys,
|
|
21
|
+
VOICE_CREDIT_USAGE,
|
|
21
22
|
type CreditDebitAttribution,
|
|
22
23
|
type Database,
|
|
23
24
|
} from "@opengeni/db";
|
|
@@ -27,6 +28,7 @@ import {
|
|
|
27
28
|
type TranscriptionBilling,
|
|
28
29
|
type TranscriptionBillingContext,
|
|
29
30
|
} from "../transcription";
|
|
31
|
+
import { voiceCreditStanding, voiceInsufficientCreditsMessage } from "./realtime-voice-billing";
|
|
30
32
|
|
|
31
33
|
/** Credit debit type and usage source for deployment-funded voice input. */
|
|
32
34
|
export const VOICE_INPUT_DEBIT_TYPE = VOICE_TRANSCRIPTION_DEBIT_TYPE;
|
|
@@ -86,8 +88,9 @@ function startOfUtcMonth(): Date {
|
|
|
86
88
|
}
|
|
87
89
|
|
|
88
90
|
/**
|
|
89
|
-
* Same admission shape as knowledge-search and session admission:
|
|
90
|
-
*
|
|
91
|
+
* Same admission shape as knowledge-search and session admission:
|
|
92
|
+
* voice-spendable credits (general plus signup, not model-scoped promotional
|
|
93
|
+
* credits) must be positive, the workspace/member
|
|
91
94
|
* allowance must not be exhausted, and a static monthly cost cap applies.
|
|
92
95
|
* Admission is a read, not a reservation; settlement is post-use and may take
|
|
93
96
|
* an account below zero by at most the concurrent in-flight calls.
|
|
@@ -132,11 +135,11 @@ export function createVoiceInputBilling(deps: {
|
|
|
132
135
|
},
|
|
133
136
|
);
|
|
134
137
|
}
|
|
135
|
-
const balance = await getSpendableCreditBalance(deps.db, accountId);
|
|
138
|
+
const balance = await getSpendableCreditBalance(deps.db, accountId, VOICE_CREDIT_USAGE);
|
|
136
139
|
if (balance.balanceMicros <= 0) {
|
|
137
140
|
throw new TranscriptionBillingRefusedError({
|
|
138
141
|
code: "insufficient_credits",
|
|
139
|
-
message: "Voice input
|
|
142
|
+
message: voiceInsufficientCreditsMessage("Voice input", voiceCreditStanding(balance)),
|
|
140
143
|
});
|
|
141
144
|
}
|
|
142
145
|
const refusal = await checkWorkspaceAllowance(deps.db, {
|
|
@@ -250,6 +253,7 @@ export async function settleVoiceInputUsage(
|
|
|
250
253
|
sourceType: VOICE_TRANSCRIPTION_SOURCE_TYPE,
|
|
251
254
|
sourceId: input.billing.sourceId,
|
|
252
255
|
idempotencyKey: keys.debitIdempotencyKey,
|
|
256
|
+
usage: VOICE_CREDIT_USAGE,
|
|
253
257
|
metadata: {
|
|
254
258
|
providerId: input.providerId,
|
|
255
259
|
model: input.model,
|
|
@@ -298,6 +302,7 @@ export async function reconcileUnsettledVoiceInputCharges(
|
|
|
298
302
|
workspaceId: input.workspaceId,
|
|
299
303
|
sourceId: charge.sourceId,
|
|
300
304
|
}).debitIdempotencyKey,
|
|
305
|
+
usage: VOICE_CREDIT_USAGE,
|
|
301
306
|
metadata: {
|
|
302
307
|
basis: "reconciled_receipt" satisfies VoiceInputBillingBasis,
|
|
303
308
|
...(charge.attribution.kind === "unknown"
|
package/src/model-catalog.ts
CHANGED
|
@@ -39,18 +39,19 @@ import {
|
|
|
39
39
|
import {
|
|
40
40
|
listConnectionsMetadata,
|
|
41
41
|
getDeploymentModelCatalog,
|
|
42
|
-
|
|
42
|
+
listWorkspaceProviderCustomModelsByKind,
|
|
43
43
|
getWorkspaceProviderCustomModelForExecution,
|
|
44
44
|
lockActiveWorkspaceProviderCustomModelForAdmission,
|
|
45
45
|
getWorkspaceGatewayCustomModelForExecution,
|
|
46
46
|
getWorkspaceOpenRouterCustomModelForExecution,
|
|
47
47
|
getOrganizationModelProviderCustomModelForExecution,
|
|
48
|
-
|
|
49
|
-
listWorkspaceOpenRouterCustomModels,
|
|
50
|
-
listOrganizationModelProviderCustomModelsForWorkspace,
|
|
48
|
+
getOrganizationModelProviderCatalogForWorkspace,
|
|
51
49
|
lockActiveOrganizationModelProviderCustomModelForAdmission,
|
|
52
50
|
type OrganizationClaudeModelAdmissionAuthority,
|
|
53
51
|
type Database,
|
|
52
|
+
type WorkspaceCustomModelProviderKind,
|
|
53
|
+
type WorkspaceGatewayCustomModel,
|
|
54
|
+
type WorkspaceOpenRouterCustomModel,
|
|
54
55
|
} from "@opengeni/db";
|
|
55
56
|
|
|
56
57
|
export type ResolvedCatalogSettings = {
|
|
@@ -249,26 +250,34 @@ export async function resolveWorkspaceCatalogSettings(
|
|
|
249
250
|
? [productModelId.slice(ORGANIZATION_OPENROUTER_MODEL_ID_PREFIX.length)]
|
|
250
251
|
: [],
|
|
251
252
|
);
|
|
253
|
+
// One scoped read per catalog family instead of one scoped transaction per
|
|
254
|
+
// provider kind. The batched readers keep each provider's filters, bound and
|
|
255
|
+
// overflow error. The Claude subscription flag is env-only (the catalog
|
|
256
|
+
// document never changes it), so it decides the requested kinds up front.
|
|
257
|
+
const workspaceCustomModelKinds: WorkspaceCustomModelProviderKind[] = [
|
|
258
|
+
"vercel_gateway",
|
|
259
|
+
"openrouter",
|
|
260
|
+
...(supportsOrganizationProviderReads
|
|
261
|
+
? CLAUDE_CONNECTION_KINDS.filter(
|
|
262
|
+
(kind) => kind !== "claude_subscription" || envSettings.claudeSubscriptionEnabled,
|
|
263
|
+
)
|
|
264
|
+
: []),
|
|
265
|
+
];
|
|
252
266
|
const [
|
|
253
267
|
resolved,
|
|
254
|
-
|
|
255
|
-
activeOpenRouterCustomModels,
|
|
268
|
+
workspaceCustomModels,
|
|
256
269
|
retainedGatewayCustomModels,
|
|
257
270
|
retainedOpenRouterCustomModels,
|
|
258
|
-
|
|
259
|
-
organizationOpenRouterCustomModels,
|
|
271
|
+
organizationProviders,
|
|
260
272
|
retainedOrganizationGatewayCustomModels,
|
|
261
273
|
retainedOrganizationOpenRouterCustomModels,
|
|
262
274
|
directConnections,
|
|
263
275
|
] = await Promise.all([
|
|
264
276
|
resolveCatalogSettings(db, envSettings),
|
|
265
|
-
|
|
266
|
-
accountId: input.accountId,
|
|
267
|
-
workspaceId: input.workspaceId,
|
|
268
|
-
}),
|
|
269
|
-
listWorkspaceOpenRouterCustomModels(db, {
|
|
277
|
+
listWorkspaceProviderCustomModelsByKind(db, {
|
|
270
278
|
accountId: input.accountId,
|
|
271
279
|
workspaceId: input.workspaceId,
|
|
280
|
+
providerKinds: workspaceCustomModelKinds,
|
|
272
281
|
}),
|
|
273
282
|
Promise.all(
|
|
274
283
|
[...new Set(retainedGatewayUpstreamModelIds)].map(
|
|
@@ -291,19 +300,12 @@ export async function resolveWorkspaceCatalogSettings(
|
|
|
291
300
|
),
|
|
292
301
|
),
|
|
293
302
|
supportsOrganizationProviderReads
|
|
294
|
-
?
|
|
295
|
-
accountId: input.accountId,
|
|
296
|
-
workspaceId: input.workspaceId,
|
|
297
|
-
providerKind: "vercel_gateway",
|
|
298
|
-
})
|
|
299
|
-
: Promise.resolve([]),
|
|
300
|
-
supportsOrganizationProviderReads
|
|
301
|
-
? listOrganizationModelProviderCustomModelsForWorkspace(db, {
|
|
303
|
+
? getOrganizationModelProviderCatalogForWorkspace(db, {
|
|
302
304
|
accountId: input.accountId,
|
|
303
305
|
workspaceId: input.workspaceId,
|
|
304
|
-
|
|
306
|
+
providerKinds: ["vercel_gateway", "openrouter", ...CLAUDE_CONNECTION_KINDS],
|
|
305
307
|
})
|
|
306
|
-
:
|
|
308
|
+
: null,
|
|
307
309
|
supportsOrganizationProviderReads
|
|
308
310
|
? Promise.all(
|
|
309
311
|
[...new Set(retainedOrganizationGatewayUpstreamModelIds)].map(
|
|
@@ -352,29 +354,25 @@ export async function resolveWorkspaceCatalogSettings(
|
|
|
352
354
|
return customModels;
|
|
353
355
|
};
|
|
354
356
|
const gatewayCustomModels = includeRetainedModels(
|
|
355
|
-
|
|
357
|
+
workspaceCustomModels.vercel_gateway as WorkspaceGatewayCustomModel[],
|
|
356
358
|
retainedGatewayCustomModels,
|
|
357
359
|
);
|
|
358
360
|
const openRouterCustomModels = includeRetainedModels(
|
|
359
|
-
|
|
361
|
+
workspaceCustomModels.openrouter as WorkspaceOpenRouterCustomModel[],
|
|
360
362
|
retainedOpenRouterCustomModels,
|
|
361
363
|
);
|
|
362
364
|
const organizationGatewayModels = includeRetainedModels(
|
|
363
|
-
|
|
365
|
+
organizationProviders?.vercel_gateway.models ?? [],
|
|
364
366
|
retainedOrganizationGatewayCustomModels,
|
|
365
367
|
);
|
|
366
368
|
const organizationOpenRouterModels = includeRetainedModels(
|
|
367
|
-
|
|
369
|
+
organizationProviders?.openrouter.models ?? [],
|
|
368
370
|
retainedOrganizationOpenRouterCustomModels,
|
|
369
371
|
);
|
|
370
372
|
const claudeConnections: ClaudeConnectionCatalog = {};
|
|
371
|
-
if (
|
|
373
|
+
if (organizationProviders)
|
|
372
374
|
for (const kind of CLAUDE_CONNECTION_KINDS) {
|
|
373
|
-
const models =
|
|
374
|
-
accountId: input.accountId,
|
|
375
|
-
workspaceId: input.workspaceId,
|
|
376
|
-
providerKind: kind,
|
|
377
|
-
});
|
|
375
|
+
const models = [...organizationProviders[kind].models];
|
|
378
376
|
const prefix = claudeProviderId(kind) + "/";
|
|
379
377
|
for (const productId of retainedProductModelIds)
|
|
380
378
|
if (productId?.startsWith(prefix)) {
|
|
@@ -392,11 +390,7 @@ export async function resolveWorkspaceCatalogSettings(
|
|
|
392
390
|
if (supportsOrganizationProviderReads)
|
|
393
391
|
for (const kind of CLAUDE_CONNECTION_KINDS) {
|
|
394
392
|
if (kind === "claude_subscription" && !resolved.settings.claudeSubscriptionEnabled) continue;
|
|
395
|
-
const models =
|
|
396
|
-
accountId: input.accountId,
|
|
397
|
-
workspaceId: input.workspaceId,
|
|
398
|
-
providerKind: kind,
|
|
399
|
-
});
|
|
393
|
+
const models = [...workspaceCustomModels[kind]];
|
|
400
394
|
const prefix = claudeProviderId(kind, "workspace") + "/";
|
|
401
395
|
for (const productId of retainedProductModelIds) {
|
|
402
396
|
if (!productId?.startsWith(prefix)) continue;
|