Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions packages/db/src/schema-types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,9 @@ export const OrganizationPlanSchema = z.enum(['teams', 'enterprise']);
export type OrganizationPlan = z.infer<typeof OrganizationPlanSchema>;

const OrganizationSettingsSchema = z.object({
/** @deprecated */
model_allow_list: z.array(z.string()).optional(),
/** @deprecated */
provider_allow_list: z.array(z.string()).optional(),

// under development, not yet enforced, will replace model_allow_list and provider_allow_list:
Expand Down
16 changes: 8 additions & 8 deletions src/app/api/organizations/[id]/defaults/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { getAuthorizedOrgContext } from '@/lib/organizations/organization-auth';
import type { NextRequest } from 'next/server';
import { PRIMARY_DEFAULT_MODEL, getFirstFreeModel, preferredModels } from '@/lib/models';
import { getEnhancedOpenRouterModels } from '@/lib/providers/openrouter';
import { createProviderAwareModelAllowPredicate } from '@/lib/model-allow.server';
import { createAllowPredicateFromDenyList } from '@/lib/model-allow.server';
import { getModelIdToProviderSlugsIndex } from '@/lib/providers/openrouter/models-by-provider-index.server';

type DefaultsResponse = {
Expand All @@ -26,9 +26,9 @@ export async function GET(
// Get organization's default model setting
let defaultModel = organization.settings?.default_model;

const allowList = organization.settings?.model_allow_list;
const denyList = organization.settings?.model_deny_list;

const isAllowed = createProviderAwareModelAllowPredicate(allowList ?? []);
const isAllowed = createAllowPredicateFromDenyList(denyList ?? []);

const findFirstAllowedModel = async (modelIds: readonly string[]) => {
for (const modelId of modelIds) {
Expand Down Expand Up @@ -73,23 +73,23 @@ export async function GET(
defaultModel = await findFirstAllowedModel([PRIMARY_DEFAULT_MODEL]);

if (!defaultModel) {
if (!allowList?.length) {
if (!denyList?.length) {
defaultModel = PRIMARY_DEFAULT_MODEL;
} else {
const firstConcreteAllowedModel = allowList.find(modelId => !modelId.endsWith('/*'));
const firstConcreteAllowedModel = denyList.find(modelId => !modelId.endsWith('/*'));
defaultModel = firstConcreteAllowedModel;
}
}

if (!defaultModel && allowList?.length) {
if (!defaultModel && denyList?.length) {
defaultModel = await findFirstAllowedModel(preferredModels);
}

if (!defaultModel && allowList?.length) {
if (!defaultModel && denyList?.length) {
defaultModel = await findFirstAllowedModelFromDbSnapshot();
}

if (!defaultModel && allowList?.length) {
if (!defaultModel && denyList?.length) {
defaultModel = await findFirstAllowedModelFromOpenRouter();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import { AvailableModelsDialog } from './providers-and-models/AvailableModelsDia
import { useOrganizationConfiguration } from './providers-and-models/useOrganizationConfiguration';
import { useOpenRouterModelsAndProviders } from '@/app/api/openrouter/hooks';
import type { ProviderSelection } from '@/components/models/util';
import { isModelAllowedProviderAwareClient } from '@/lib/model-allow.client';
import { normalizeModelId } from '@/lib/model-utils';

type OrganizationProvidersAndModelsConfigurationCardProps = {
organizationId: string;
Expand Down Expand Up @@ -67,8 +67,7 @@ export function computeProviderSelectionsForSummaryCard(params: {
.filter(model => {
if (!model.endpoint) return false;
return (
modelAllowList.length === 0 ||
isModelAllowedProviderAwareClient(model.slug, modelAllowList, openRouterProviders)
modelAllowList.length === 0 || modelAllowList.includes(normalizeModelId(model.slug))
);
})
.map(model => model.slug);
Expand All @@ -86,7 +85,7 @@ export function computeProviderSelectionsForSummaryCard(params: {
const selectedModels = provider.models
.filter(model => {
if (!model.endpoint) return false;
return isModelAllowedProviderAwareClient(model.slug, modelAllowList, openRouterProviders);
return modelAllowList.includes(normalizeModelId(model.slug));
})
.map(model => model.slug);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { isModelAllowedProviderAwareClient } from '@/lib/model-allow.client';
import { normalizeModelId } from '@/lib/model-utils';

export type OpenRouterModelSlugSnapshot = {
Expand Down Expand Up @@ -93,7 +92,7 @@ export function computeEnabledProviderSlugs(
export function computeAllowedModelIds(
draftModelAllowList: ReadonlyArray<string>,
openRouterModels: ReadonlyArray<OpenRouterModelSlugSnapshot>,
openRouterProviders: OpenRouterProviderModelsSnapshot
_openRouterProviders: OpenRouterProviderModelsSnapshot
): Set<string> {
const allowed = new Set<string>();

Expand All @@ -107,12 +106,7 @@ export function computeAllowedModelIds(
const allowListArray = [...draftModelAllowList];
for (const model of openRouterModels) {
const normalizedModelId = normalizeModelId(model.slug);
const isAllowed = isModelAllowedProviderAwareClient(
normalizedModelId,
allowListArray,
openRouterProviders
);
if (isAllowed) {
if (allowListArray.includes(normalizedModelId)) {
allowed.add(normalizedModelId);
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import {
useOpenRouterProviders,
} from '@/app/api/openrouter/hooks';
import type { OpenRouterProvider } from '@/lib/organizations/organization-types';
import { isModelAllowedProviderAwareClient } from '@/lib/model-allow.client';
import { normalizeModelId } from '@/lib/model-utils';

export type ConfigurationData = {
allModelsSelected: boolean;
Expand All @@ -23,8 +23,7 @@ export function useOrganizationConfiguration(organizationId: string) {
useOrganizationWithMembers(organizationId);
const { data: modelsData, isLoading: modelsLoading } = useOpenRouterModels();
const { data: providersData, isLoading: providersLoading } = useOpenRouterProviders();
const { providers: openRouterProviders, isLoading: providersSnapshotLoading } =
useOpenRouterModelsAndProviders();
const { isLoading: providersSnapshotLoading } = useOpenRouterModelsAndProviders();

const isLoading = orgLoading || modelsLoading || providersLoading || providersSnapshotLoading;

Expand Down Expand Up @@ -55,14 +54,14 @@ export function useOrganizationConfiguration(organizationId: string) {
displayModelAllowList = []; // No exclusions
} else {
const allowedModelCount = allModelIds.filter(modelId =>
isModelAllowedProviderAwareClient(modelId, savedModelAllowList, openRouterProviders)
savedModelAllowList.includes(normalizeModelId(modelId))
).length;
const modelAllowRatio = allowedModelCount / allModelIds.length;
// If more than 50% are allowed, treat as "all selected" mode with exclusions
if (modelAllowRatio > 0.5) {
allModelsSelected = true;
displayModelAllowList = allModelIds.filter(
id => !isModelAllowedProviderAwareClient(id, savedModelAllowList, openRouterProviders)
id => !savedModelAllowList.includes(normalizeModelId(id))
);
} else {
allModelsSelected = false;
Expand Down
8 changes: 4 additions & 4 deletions src/lib/integrations/discord-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { DISCORD_CLIENT_ID, DISCORD_CLIENT_SECRET, DISCORD_BOT_TOKEN } from '@/l
import { APP_URL } from '@/lib/constants';
import { getOrganizationById } from '@/lib/organizations/organizations';
import { getDefaultAllowedModel } from '@/lib/slack-bot/model-allow-list';
import { createProviderAwareModelAllowPredicate } from '@/lib/model-allow.server';
import { createAllowPredicateFromDenyList } from '@/lib/model-allow.server';
import { minimax_m25_free_model } from '@/lib/providers/minimax';
import { CLAUDE_OPUS_CURRENT_MODEL_ID } from '@/lib/providers/anthropic';

Expand Down Expand Up @@ -367,9 +367,9 @@ export async function updateModel(
if (owner.type === 'org') {
const organization = await getOrganizationById(owner.id);
if (organization) {
const modelAllowList = organization.settings?.model_allow_list || [];
if (modelAllowList.length > 0) {
const isAllowed = createProviderAwareModelAllowPredicate(modelAllowList);
const modelDenyList = organization.settings?.model_deny_list || [];
if (modelDenyList.length > 0) {
const isAllowed = createAllowPredicateFromDenyList(modelDenyList);
if (!(await isAllowed(modelSlug))) {
return { success: false, error: 'Model is not allowed by organization policy' };
}
Expand Down
8 changes: 4 additions & 4 deletions src/lib/integrations/slack-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { WebClient } from '@slack/web-api';
import type { OAuthV2Response } from '@slack/oauth';
import { getOrganizationById } from '@/lib/organizations/organizations';
import { getDefaultAllowedModel } from '@/lib/slack-bot/model-allow-list';
import { createProviderAwareModelAllowPredicate } from '@/lib/model-allow.server';
import { createAllowPredicateFromDenyList } from '@/lib/model-allow.server';
import { minimax_m25_free_model } from '@/lib/providers/minimax';
import { CLAUDE_OPUS_CURRENT_MODEL_ID } from '@/lib/providers/anthropic';

Expand Down Expand Up @@ -481,9 +481,9 @@ export async function updateModel(
if (owner.type === 'org') {
const organization = await getOrganizationById(owner.id);
if (organization) {
const modelAllowList = organization.settings?.model_allow_list || [];
if (modelAllowList.length > 0) {
const isAllowed = createProviderAwareModelAllowPredicate(modelAllowList);
const modelDenyList = organization.settings?.model_deny_list || [];
if (modelDenyList.length > 0) {
const isAllowed = createAllowPredicateFromDenyList(modelDenyList);
if (!(await isAllowed(modelSlug))) {
return { success: false, error: 'Model is not allowed by organization policy' };
}
Expand Down
40 changes: 0 additions & 40 deletions src/lib/model-allow.client.test.ts

This file was deleted.

82 changes: 0 additions & 82 deletions src/lib/model-allow.client.ts

This file was deleted.

11 changes: 11 additions & 0 deletions src/lib/model-allow.server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type ProviderAwareAllowPredicateOptions = {

export type ProviderAwareAllowPredicate = (modelId: string) => Promise<boolean>;

/** @deprecated Use `createAllowPredicateFromDenyList` instead */
export function createProviderAwareModelAllowPredicate(
allowList: string[],
options?: ProviderAwareAllowPredicateOptions
Expand Down Expand Up @@ -48,6 +49,16 @@ export function createProviderAwareModelAllowPredicate(
};
}

export function createAllowPredicateFromDenyList(
denyList: string[] | undefined
): ProviderAwareAllowPredicate {
const denyListSet = new Set(denyList);
return (modelId: string): Promise<boolean> => {
const normalizedModelId = normalizeModelId(modelId);
return Promise.resolve(!denyListSet.has(normalizedModelId));
};
}

export async function createDenyLists(
model_allow_list: string[] | undefined,
provider_allow_list: string[] | undefined
Expand Down
12 changes: 6 additions & 6 deletions src/lib/slack-bot/model-allow-list.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { PRIMARY_DEFAULT_MODEL, preferredModels } from '@/lib/models';
import { getOrganizationById } from '@/lib/organizations/organizations';
import { createProviderAwareModelAllowPredicate } from '@/lib/model-allow.server';
import { createAllowPredicateFromDenyList } from '@/lib/model-allow.server';

/**
* Get a default model that is allowed for an organization.
Expand All @@ -15,14 +15,14 @@ export async function getDefaultAllowedModel(
return globalDefault;
}

const modelAllowList = organization.settings?.model_allow_list || [];
const modelDenyList = organization.settings?.model_deny_list || [];

// If no restrictions, use global default
if (modelAllowList.length === 0) {
if (modelDenyList.length === 0) {
return globalDefault;
}

const isAllowed = createProviderAwareModelAllowPredicate(modelAllowList);
const isAllowed = createAllowPredicateFromDenyList(modelDenyList);

// Check if the organization's default model is allowed
const orgDefaultModel = organization.settings?.default_model;
Expand All @@ -42,15 +42,15 @@ export async function getDefaultAllowedModel(
}

// Fall back to the first non-wildcard model in the allow list
const firstNonWildcard = modelAllowList.find(m => !m.endsWith('/*'));
const firstNonWildcard = modelDenyList.find(m => !m.endsWith('/*'));
if (firstNonWildcard) {
return firstNonWildcard;
}

// If only wildcards, fall back to global default (admin misconfiguration)
console.warn(
'[SlackBot] Organization has only wildcard entries in model allow list:',
modelAllowList
modelDenyList
);
return globalDefault;
}
Loading
Loading