From d5e5cfc5ff0697e7e8a17a10d3157489d4af0686 Mon Sep 17 00:00:00 2001 From: Ridhwan Hussain <73362400+TheUntraceable@users.noreply.github.com.> Date: Wed, 10 Jun 2026 15:46:14 +0100 Subject: [PATCH] fix: let people do negative markups for free models --- server/src/internal/features/featureUtils.ts | 10 ++++++---- shared/api/features/apiFeatureV1.ts | 5 +++-- shared/api/features/crud/common/baseFeatureParamsV1.ts | 4 ++-- shared/drizzle/0002_shocking_wong.sql | 1 + .../models/featureModels/featureConfig/creditConfig.ts | 6 +++--- 5 files changed, 15 insertions(+), 11 deletions(-) create mode 100644 shared/drizzle/0002_shocking_wong.sql diff --git a/server/src/internal/features/featureUtils.ts b/server/src/internal/features/featureUtils.ts index cf8eb5311..35e66c579 100644 --- a/server/src/internal/features/featureUtils.ts +++ b/server/src/internal/features/featureUtils.ts @@ -80,9 +80,10 @@ export const validateCreditSystem = ( const defaultMarkup = newConfig.default_markup; if (defaultMarkup != null) { const parsedDefaultMarkup = Number(defaultMarkup); - if (Number.isNaN(parsedDefaultMarkup) || parsedDefaultMarkup < 0) { + if (Number.isNaN(parsedDefaultMarkup) || parsedDefaultMarkup < -100) { throw new RecaseError({ - message: "Default markup should be a non-negative number", + message: + "Default markup must be -100 or greater (-100 makes usage free)", code: ErrCode.InvalidFeature, statusCode: 400, }); @@ -94,9 +95,10 @@ export const validateCreditSystem = ( if (providerMarkups != null) { for (const [provider, entry] of Object.entries(providerMarkups)) { const markup = Number(entry?.markup); - if (!provider || Number.isNaN(markup) || markup < 0) { + if (!provider || Number.isNaN(markup) || markup < -100) { throw new RecaseError({ - message: "Provider markups must be non-negative numbers", + message: + "Provider markups must be -100 or greater (-100 makes usage free)", code: ErrCode.InvalidFeature, statusCode: 400, }); diff --git a/shared/api/features/apiFeatureV1.ts b/shared/api/features/apiFeatureV1.ts index 9ff81d6f4..0f63a62c4 100644 --- a/shared/api/features/apiFeatureV1.ts +++ b/shared/api/features/apiFeatureV1.ts @@ -50,8 +50,9 @@ export const ApiFeatureV1Schema = z.object({ description: "Per-model markup overrides for AI credit systems.", }), - default_markup: z.number().min(0).optional().meta({ - description: "Default percentage markup for AI credit systems.", + default_markup: z.number().min(-100).optional().meta({ + description: + "Default percentage markup for AI credit systems. Use -100 to make usage free.", }), provider_markups: ProviderMarkupsSchema.optional().meta({ diff --git a/shared/api/features/crud/common/baseFeatureParamsV1.ts b/shared/api/features/crud/common/baseFeatureParamsV1.ts index e9a103ed7..7394be049 100644 --- a/shared/api/features/crud/common/baseFeatureParamsV1.ts +++ b/shared/api/features/crud/common/baseFeatureParamsV1.ts @@ -55,9 +55,9 @@ export const BaseFeatureV1ParamsSchema = z.object({ "Per-model markup overrides for AI credit systems. Maps model IDs to their markup configuration.", }), - default_markup: z.number().min(0).optional().meta({ + default_markup: z.number().min(-100).optional().meta({ description: - "Default percentage markup for this AI credit system. Used when no model or provider markup applies.", + "Default percentage markup for this AI credit system. Used when no model or provider markup applies. Use -100 to make usage free.", }), provider_markups: ProviderMarkupsSchema.optional().meta({ diff --git a/shared/drizzle/0002_shocking_wong.sql b/shared/drizzle/0002_shocking_wong.sql new file mode 100644 index 000000000..be0692e60 --- /dev/null +++ b/shared/drizzle/0002_shocking_wong.sql @@ -0,0 +1 @@ +ALTER TABLE "features" ADD COLUMN "model_markups" jsonb DEFAULT null; \ No newline at end of file diff --git a/shared/models/featureModels/featureConfig/creditConfig.ts b/shared/models/featureModels/featureConfig/creditConfig.ts index 69f51666a..63d524d42 100644 --- a/shared/models/featureModels/featureConfig/creditConfig.ts +++ b/shared/models/featureModels/featureConfig/creditConfig.ts @@ -8,7 +8,7 @@ export const CreditSchemaItemSchema = z.object({ }); const MarkupEntrySchema = z.object({ - markup: z.number().min(0), // percentage markup, e.g. 20 for 20% + markup: z.number().min(-100), // percentage markup, e.g. 20 for 20%, -100 for free }); export const ProviderMarkupsSchema = z @@ -26,7 +26,7 @@ export const CreditSystemConfigSchema = z.object({ }), ), usage_type: z.nativeEnum(FeatureUsageType), - default_markup: z.number().min(0).optional(), + default_markup: z.number().min(-100).optional(), provider_markups: ProviderMarkupsSchema, }); @@ -34,7 +34,7 @@ export const ModelMarkupsSchema = z .record( z.string(), // Represents the model name in "provider/model" format, e.g. "anthropic/claude-2" MarkupEntrySchema.extend({ - markup: z.number().min(0).optional(), // Omit to inherit provider/global markup + markup: z.number().min(-100).optional(), // Omit to inherit provider/global markup input_cost: z.number().min(0).optional(), // $/M tokens, required for custom/ models output_cost: z.number().min(0).optional(), // $/M tokens, required for custom/ models }),