kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
6f35cffae9
commit
8b039708ff
|
@ -104,7 +104,7 @@ export const authRouter = issuer({
|
|||
},
|
||||
partialUser: {
|
||||
email: ghUser.email,
|
||||
emailVerified: true,
|
||||
isEmailVerified: true,
|
||||
name: ghUser.name || undefined,
|
||||
username: ghUser.login.toLowerCase(),
|
||||
image: ghUser.avatar_url
|
||||
|
@ -118,7 +118,7 @@ export const authRouter = issuer({
|
|||
},
|
||||
partialUser: {
|
||||
email: value.email,
|
||||
emailVerified: true
|
||||
isEmailVerified: true
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
import {
|
||||
agenticProjectConfigSchema,
|
||||
type DeploymentOriginAdapter,
|
||||
deploymentOriginAdapterSchema,
|
||||
pricingIntervalListSchema,
|
||||
type PricingPlanList,
|
||||
pricingPlanListSchema
|
||||
} from '@agentic/platform-schemas'
|
||||
|
@ -29,6 +31,7 @@ import {
|
|||
createUpdateSchema,
|
||||
deploymentIdentifier,
|
||||
deploymentPrimaryId,
|
||||
pricingIntervalEnum,
|
||||
projectId,
|
||||
teamId,
|
||||
timestamps,
|
||||
|
@ -65,11 +68,10 @@ export const deployments = pgTable(
|
|||
onDelete: 'cascade'
|
||||
}),
|
||||
|
||||
// TODO: Tool definitions or OpenAPI spec
|
||||
// services: jsonb().$type<Service[]>().default([]),
|
||||
// TODO: Tool definitions
|
||||
// tools: jsonb().$type<Tool[]>().default([]),
|
||||
|
||||
// TODO: metadata config (logo, keywords, examples, etc)
|
||||
// TODO: openapi spec or tool definitions or mcp adapter
|
||||
// TODO: webhooks
|
||||
// TODO: third-party auth provider config
|
||||
// NOTE: will need consumer.authProviders as well as user.authProviders for
|
||||
|
@ -84,7 +86,10 @@ export const deployments = pgTable(
|
|||
originAdapter: jsonb().$type<DeploymentOriginAdapter>().notNull(),
|
||||
|
||||
// Array<PricingPlan>
|
||||
pricingPlans: jsonb().$type<PricingPlanList>().notNull()
|
||||
pricingPlans: jsonb().$type<PricingPlanList>().notNull(),
|
||||
|
||||
// Which pricing intervals are supported for subscriptions to this project
|
||||
pricingIntervals: pricingIntervalEnum().array().default(['month']).notNull()
|
||||
|
||||
// coupons: jsonb().$type<Coupon[]>().default([]).notNull()
|
||||
},
|
||||
|
@ -135,8 +140,9 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
|
|||
message: 'Invalid deployment hash'
|
||||
}),
|
||||
|
||||
originAdapter: deploymentOriginAdapterSchema,
|
||||
pricingPlans: pricingPlanListSchema,
|
||||
originAdapter: deploymentOriginAdapterSchema
|
||||
pricingIntervals: pricingIntervalListSchema
|
||||
})
|
||||
.omit({
|
||||
originUrl: true
|
||||
|
@ -159,33 +165,7 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
|
|||
.openapi('Deployment')
|
||||
|
||||
export const deploymentInsertSchema = createInsertSchema(deployments, {
|
||||
projectId: projectIdSchema,
|
||||
|
||||
iconUrl: (schema) =>
|
||||
schema
|
||||
.url()
|
||||
.describe(
|
||||
'Logo image URL to use for this deployment. Logos should have a square aspect ratio.'
|
||||
),
|
||||
|
||||
sourceUrl: (schema) => schema.url(),
|
||||
|
||||
originUrl: (schema) =>
|
||||
schema.url().describe(`Base URL of the externally hosted origin API server.
|
||||
|
||||
NOTE: Agentic currently only supports \`external\` API servers. If you'd like to host your API or MCP server on Agentic's infrastructure, please reach out to support@agentic.so.`),
|
||||
|
||||
pricingPlans: pricingPlanListSchema.describe(
|
||||
'List of PricingPlans should be available as subscriptions for this deployment.'
|
||||
),
|
||||
originAdapter: deploymentOriginAdapterSchema.default({
|
||||
location: 'external',
|
||||
type: 'raw'
|
||||
})
|
||||
// .optional()
|
||||
|
||||
// TODO
|
||||
// coupons: z.array(couponSchema).optional()
|
||||
projectId: projectIdSchema
|
||||
})
|
||||
.omit({
|
||||
id: true,
|
||||
|
@ -195,8 +175,15 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
|||
userId: true,
|
||||
teamId: true
|
||||
})
|
||||
.extend({
|
||||
...agenticProjectConfigSchema.shape
|
||||
})
|
||||
.strict()
|
||||
|
||||
// TODO: Deployments should be immutable, so we should not allow updates aside
|
||||
// from publishing. But editing a project's description should be possible from
|
||||
// the admin UI, so maybe we allow only updates to some properties? Or we
|
||||
// denormalize these fields in `project`?
|
||||
export const deploymentUpdateSchema = createUpdateSchema(deployments)
|
||||
.pick({
|
||||
deletedAt: true,
|
||||
|
|
|
@ -10,7 +10,6 @@ import {
|
|||
import { validators } from '@agentic/platform-validators'
|
||||
import { relations } from '@fisch0920/drizzle-orm'
|
||||
import {
|
||||
boolean,
|
||||
index,
|
||||
integer,
|
||||
jsonb,
|
||||
|
@ -18,7 +17,6 @@ import {
|
|||
text,
|
||||
uniqueIndex
|
||||
} from '@fisch0920/drizzle-orm/pg-core'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
import {
|
||||
deploymentIdSchema,
|
||||
|
@ -69,21 +67,18 @@ export const projects = pgTable(
|
|||
applicationFeePercent: integer().default(20).notNull(),
|
||||
|
||||
// TODO: This is going to need to vary from dev to prod
|
||||
isStripeConnectEnabled: boolean().default(false).notNull(),
|
||||
|
||||
// Which pricing intervals are supported for subscriptions to this project
|
||||
pricingIntervals: pricingIntervalEnum()
|
||||
.array()
|
||||
.default(['month'])
|
||||
.notNull(),
|
||||
//isStripeConnectEnabled: boolean().default(false).notNull(),
|
||||
|
||||
// Default pricing interval for subscriptions to this project
|
||||
// Note: This is essentially hard-coded and not configurable by users for now.
|
||||
defaultPricingInterval: pricingIntervalEnum().default('month').notNull(),
|
||||
|
||||
// Pricing currency used across all prices and subscriptions to this project
|
||||
pricingCurrency: pricingCurrencyEnum().default('usd').notNull(),
|
||||
|
||||
// All deployments share the same underlying proxy secret
|
||||
// All deployments share the same underlying proxy secret, which allows
|
||||
// origin servers to verify that requests are coming from Agentic's API
|
||||
// gateway.
|
||||
_secret: text().notNull(),
|
||||
|
||||
// Auth token used to access the platform API on behalf of this project
|
||||
|
@ -179,7 +174,6 @@ export const projectSelectSchema = createSelectSchema(projects, {
|
|||
|
||||
applicationFeePercent: (schema) => schema.nonnegative(),
|
||||
|
||||
pricingIntervals: z.array(pricingIntervalSchema).nonempty(),
|
||||
defaultPricingInterval: pricingIntervalSchema,
|
||||
|
||||
_stripeProductIdMap: stripeProductIdMapSchema,
|
||||
|
|
|
@ -19,8 +19,6 @@ import {
|
|||
userRoleEnum
|
||||
} from './common'
|
||||
|
||||
// This table is mostly managed by better-auth.
|
||||
|
||||
export const users = pgTable(
|
||||
'users',
|
||||
{
|
||||
|
@ -32,10 +30,10 @@ export const users = pgTable(
|
|||
|
||||
name: text(),
|
||||
email: text().notNull().unique(),
|
||||
emailVerified: boolean().default(false).notNull(),
|
||||
isEmailVerified: boolean().default(false).notNull(),
|
||||
image: text(),
|
||||
|
||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||
//isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||
|
||||
stripeCustomerId: stripeId()
|
||||
},
|
||||
|
@ -59,7 +57,7 @@ export const userSelectSchema = createSelectSchema(users)
|
|||
export const userUpdateSchema = createUpdateSchema(users)
|
||||
.pick({
|
||||
name: true,
|
||||
image: true,
|
||||
isStripeConnectEnabledByDefault: true
|
||||
image: true
|
||||
//isStripeConnectEnabledByDefault: true
|
||||
})
|
||||
.strict()
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
import type {
|
||||
PricingInterval,
|
||||
PricingPlan,
|
||||
PricingPlanLineItem,
|
||||
PricingPlanList
|
||||
PricingPlanLineItem
|
||||
} from '@agentic/platform-schemas'
|
||||
import { hashObject } from '@agentic/platform-core'
|
||||
|
||||
|
@ -62,30 +60,3 @@ export function getStripePriceIdForPricingPlanLineItem({
|
|||
|
||||
return project._stripePriceIdMap[pricingPlanLineItemHash]
|
||||
}
|
||||
|
||||
export function getPricingPlansByInterval({
|
||||
pricingInterval,
|
||||
pricingPlans
|
||||
}: {
|
||||
pricingInterval: PricingInterval
|
||||
pricingPlans: PricingPlanList
|
||||
}): PricingPlan[] {
|
||||
return pricingPlans.filter(
|
||||
(pricingPlan) =>
|
||||
pricingPlan.interval === undefined ||
|
||||
pricingPlan.interval === pricingInterval
|
||||
)
|
||||
}
|
||||
|
||||
const pricingIntervalToLabelMap: Record<PricingInterval, string> = {
|
||||
day: 'daily',
|
||||
week: 'weekly',
|
||||
month: 'monthly',
|
||||
year: 'yearly'
|
||||
}
|
||||
|
||||
export function getLabelForPricingInterval(
|
||||
pricingInterval: PricingInterval
|
||||
): string {
|
||||
return pricingIntervalToLabelMap[pricingInterval]
|
||||
}
|
||||
|
|
|
@ -38,7 +38,10 @@ export async function upsertOrLinkUserAccount({
|
|||
partialUser: Simplify<
|
||||
SetRequired<
|
||||
Partial<
|
||||
Pick<RawUser, 'email' | 'name' | 'username' | 'image' | 'emailVerified'>
|
||||
Pick<
|
||||
RawUser,
|
||||
'email' | 'name' | 'username' | 'image' | 'isEmailVerified'
|
||||
>
|
||||
>,
|
||||
'email'
|
||||
>
|
||||
|
|
|
@ -1,17 +1,16 @@
|
|||
import type {
|
||||
PricingPlan,
|
||||
PricingPlanLineItem
|
||||
} from '@agentic/platform-schemas'
|
||||
import type Stripe from 'stripe'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import {
|
||||
getLabelForPricingInterval,
|
||||
type PricingPlan,
|
||||
type PricingPlanLineItem
|
||||
} from '@agentic/platform-schemas'
|
||||
import pAll from 'p-all'
|
||||
|
||||
import {
|
||||
db,
|
||||
eq,
|
||||
getLabelForPricingInterval,
|
||||
getPricingPlanLineItemHashForStripePrice,
|
||||
getPricingPlansByInterval,
|
||||
type RawDeployment,
|
||||
type RawProject,
|
||||
schema
|
||||
|
@ -28,8 +27,12 @@ import { stripe } from '@/lib/external/stripe'
|
|||
* `_stripeMeterIdMap`, and `_stripePriceIdMap` fields of the given `project`.
|
||||
*
|
||||
* The `project` will be updated in the DB with any changes.
|
||||
*
|
||||
* The `deployment` is readonly and will not be updated, since all Stripe
|
||||
* resources persist on its Project in case they're the same across deployments.
|
||||
*
|
||||
* @note This function assumes that the deployment's pricing config has already
|
||||
* been validated.
|
||||
*/
|
||||
export async function upsertStripePricing({
|
||||
deployment,
|
||||
|
@ -244,25 +247,6 @@ export async function upsertStripePricing({
|
|||
}
|
||||
|
||||
const upserts: Array<() => Promise<void>> = []
|
||||
|
||||
// Validate deployment pricing plans to ensure they contain at least one valid
|
||||
// plan per pricing interval configured on the project.
|
||||
// TODO: move some of this `pricingPlans` validation to a separate function?
|
||||
// We really wouldn't want to create some resources and then fail partway when
|
||||
// this validation or some of the validation above fails.
|
||||
for (const pricingInterval of project.pricingIntervals) {
|
||||
const pricingPlans = getPricingPlansByInterval({
|
||||
pricingInterval,
|
||||
pricingPlans: deployment.pricingPlans
|
||||
})
|
||||
|
||||
assert(
|
||||
pricingPlans.length > 0,
|
||||
400,
|
||||
`Invalid pricing config for deployment "${deployment.id}": no pricing plans for interval "${pricingInterval}"`
|
||||
)
|
||||
}
|
||||
|
||||
for (const pricingPlan of deployment.pricingPlans) {
|
||||
for (const pricingPlanLineItem of pricingPlan.lineItems) {
|
||||
upserts.push(() =>
|
||||
|
|
|
@ -119,7 +119,7 @@ export async function upsertStripeSubscription(
|
|||
assert(
|
||||
priceId,
|
||||
500,
|
||||
`Error updating stripe subscription: missing expected Stripe Price for plan "${pricingPlan.slug}" line item "${lineItem.slug}"`
|
||||
`Error updating stripe subscription: missing expected Stripe Price for plan "${pricingPlan.slug}" line-item "${lineItem.slug}"`
|
||||
)
|
||||
|
||||
// An existing Stripe Subscription Item may or may not exist for this
|
||||
|
@ -199,9 +199,10 @@ export async function upsertStripeSubscription(
|
|||
updateParams.cancel_at_period_end = true
|
||||
}
|
||||
|
||||
if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||
updateParams.application_fee_percent = project.applicationFeePercent
|
||||
}
|
||||
// TODO: Stripe Connect
|
||||
// if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||
// updateParams.application_fee_percent = project.applicationFeePercent
|
||||
// }
|
||||
|
||||
subscription = await stripe.subscriptions.update(
|
||||
consumer._stripeSubscriptionId,
|
||||
|
@ -274,9 +275,10 @@ export async function upsertStripeSubscription(
|
|||
createParams.trial_period_days = pricingPlan.trialPeriodDays
|
||||
}
|
||||
|
||||
if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||
createParams.application_fee_percent = project.applicationFeePercent
|
||||
}
|
||||
// TODO: Stripe Connect
|
||||
// if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||
// createParams.application_fee_percent = project.applicationFeePercent
|
||||
// }
|
||||
|
||||
logger.debug('subscription', action, { items })
|
||||
subscription = await stripe.subscriptions.create(
|
||||
|
@ -321,7 +323,7 @@ export async function upsertStripeSubscription(
|
|||
assert(
|
||||
stripeSubscriptionItem,
|
||||
500,
|
||||
`Error post-processing stripe subscription for line item "${lineItem.slug}" on plan "${pricingPlan.slug}"`
|
||||
`Error post-processing stripe subscription for line-item "${lineItem.slug}" on plan "${pricingPlan.slug}"`
|
||||
)
|
||||
|
||||
consumerUpdate._stripeSubscriptionItemIdMap![lineItem.slug] =
|
||||
|
@ -329,7 +331,7 @@ export async function upsertStripeSubscription(
|
|||
assert(
|
||||
consumerUpdate._stripeSubscriptionItemIdMap![lineItem.slug],
|
||||
500,
|
||||
`Error post-processing stripe subscription for line item "${lineItem.slug}" on plan "${pricingPlan.slug}"`
|
||||
`Error post-processing stripe subscription for line-item "${lineItem.slug}" on plan "${pricingPlan.slug}"`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -320,9 +320,8 @@ export interface components {
|
|||
role: "user" | "admin";
|
||||
name?: string;
|
||||
email: string;
|
||||
emailVerified: boolean;
|
||||
isEmailVerified: boolean;
|
||||
image?: string;
|
||||
isStripeConnectEnabledByDefault: boolean;
|
||||
stripeCustomerId?: string;
|
||||
};
|
||||
Team: {
|
||||
|
@ -367,8 +366,6 @@ export interface components {
|
|||
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
||||
lastDeploymentId?: string;
|
||||
applicationFeePercent: number;
|
||||
isStripeConnectEnabled: boolean;
|
||||
pricingIntervals: components["schemas"]["PricingInterval"][];
|
||||
defaultPricingInterval: components["schemas"]["PricingInterval"];
|
||||
/** @enum {string} */
|
||||
pricingCurrency: "usd";
|
||||
|
@ -419,7 +416,9 @@ export interface components {
|
|||
/** @example API calls */
|
||||
label: string;
|
||||
RateLimit: {
|
||||
interval: number;
|
||||
/** @description The interval at which the rate limit is applied. Either a number in seconds or a valid [ms](https://github.com/vercel/ms) string (eg, "10s", "1m", "1h", "1d", "1w", "1y", etc). */
|
||||
interval: number | string;
|
||||
/** @description Maximum number of operations per interval (unitless). */
|
||||
maxPerInterval: number;
|
||||
};
|
||||
PricingPlanTier: {
|
||||
|
@ -427,17 +426,15 @@ export interface components {
|
|||
flatAmount?: number;
|
||||
upTo: number | "inf";
|
||||
};
|
||||
/** @description PricingPlanLineItems represent a single line-item in a Stripe Subscription. They map to a Stripe billing `Price` and possibly a corresponding Stripe `Meter` for metered usage. */
|
||||
/** @description PricingPlanLineItems represent a single line-item in a Stripe Subscription. They map to a Stripe billing `Price` and possibly a corresponding Stripe `Meter` for usage-based line-items. */
|
||||
PricingPlanLineItem: {
|
||||
slug: string | "base" | "requests";
|
||||
interval?: components["schemas"]["PricingInterval"];
|
||||
label?: components["schemas"]["label"];
|
||||
/** @enum {string} */
|
||||
usageType: "licensed";
|
||||
amount: number;
|
||||
} | {
|
||||
slug: string | "base" | "requests";
|
||||
interval?: components["schemas"]["PricingInterval"];
|
||||
label?: components["schemas"]["label"];
|
||||
/** @enum {string} */
|
||||
usageType: "metered";
|
||||
|
@ -461,11 +458,13 @@ export interface components {
|
|||
name: components["schemas"]["name"];
|
||||
slug: components["schemas"]["slug"];
|
||||
interval?: components["schemas"]["PricingInterval"];
|
||||
desc?: string;
|
||||
description?: string;
|
||||
features?: string[];
|
||||
trialPeriodDays?: number;
|
||||
lineItems: components["schemas"]["PricingPlanLineItem"][];
|
||||
};
|
||||
/** @description List of billing intervals for subscriptions. */
|
||||
PricingIntervalList: components["schemas"]["PricingInterval"][];
|
||||
Deployment: {
|
||||
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
||||
id: string;
|
||||
|
@ -489,6 +488,7 @@ export interface components {
|
|||
originAdapter: components["schemas"]["DeploymentOriginAdapter"];
|
||||
/** @description List of PricingPlans */
|
||||
pricingPlans: components["schemas"]["PricingPlan"][];
|
||||
pricingIntervals: components["schemas"]["PricingIntervalList"];
|
||||
};
|
||||
};
|
||||
responses: {
|
||||
|
@ -608,7 +608,6 @@ export interface operations {
|
|||
"application/json": {
|
||||
name?: string;
|
||||
image?: string;
|
||||
isStripeConnectEnabledByDefault?: boolean;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
@ -1270,27 +1269,50 @@ export interface operations {
|
|||
identifier: string;
|
||||
version?: string;
|
||||
published?: boolean;
|
||||
/** @description A short description of the project. */
|
||||
description?: string;
|
||||
/** @description A readme documenting the project (supports GitHub-flavored markdown). */
|
||||
readme?: string;
|
||||
/**
|
||||
* Format: uri
|
||||
* @description Logo image URL to use for this deployment. Logos should have a square aspect ratio.
|
||||
* @description Optional logo image URL to use for the project. Logos should have a square aspect ratio.
|
||||
*/
|
||||
iconUrl?: string;
|
||||
/** Format: uri */
|
||||
/**
|
||||
* Format: uri
|
||||
* @description Optional URL to the source code of the project (eg, GitHub repo).
|
||||
*/
|
||||
sourceUrl?: string;
|
||||
/** @description Project id (e.g. "proj_tz4a98xxat96iws9zmbrgj3a") */
|
||||
projectId: string;
|
||||
/**
|
||||
* Format: uri
|
||||
* @description Base URL of the externally hosted origin API server.
|
||||
* @description Required base URL of the externally hosted origin API server. Must be a valid `https` URL.
|
||||
*
|
||||
* NOTE: Agentic currently only supports `external` API servers. If you'd like to host your API or MCP server on Agentic's infrastructure, please reach out to support@agentic.so.
|
||||
*/
|
||||
originUrl: string;
|
||||
originAdapter?: components["schemas"]["DeploymentOriginAdapter"] & unknown;
|
||||
/** @description List of PricingPlans should be available as subscriptions for this deployment. */
|
||||
pricingPlans: components["schemas"]["PricingPlan"][];
|
||||
/**
|
||||
* @description List of PricingPlans configuring which Stripe subscriptions should be available for the project. Defaults to a single free plan which is useful for developing and testing.your project.
|
||||
* @default [
|
||||
* {
|
||||
* "name": "Free",
|
||||
* "slug": "free",
|
||||
* "lineItems": [
|
||||
* {
|
||||
* "slug": "base",
|
||||
* "usageType": "licensed",
|
||||
* "amount": 0
|
||||
* }
|
||||
* ]
|
||||
* }
|
||||
* ]
|
||||
*/
|
||||
pricingPlans?: components["schemas"]["PricingPlan"][];
|
||||
pricingIntervals?: components["schemas"]["PricingIntervalList"] & unknown;
|
||||
/** @description Name of the project. */
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
};
|
||||
|
|
|
@ -2,7 +2,7 @@ import { z } from '@hono/zod-openapi'
|
|||
|
||||
import {
|
||||
deploymentOriginAdapterSchema,
|
||||
pricingIntervalSchema,
|
||||
pricingIntervalListSchema,
|
||||
type PricingPlan,
|
||||
pricingPlanListSchema
|
||||
} from './schemas'
|
||||
|
@ -54,13 +54,6 @@ export const agenticProjectConfigSchema = z.object({
|
|||
)
|
||||
.optional(),
|
||||
|
||||
/** Optional URL to the source code of the project. */
|
||||
sourceUrl: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.describe('Optional URL to the source code of the project.'),
|
||||
|
||||
/**
|
||||
* Optional logo image URL to use for the project. Logos should have a square aspect ratio.
|
||||
*/
|
||||
|
@ -72,6 +65,15 @@ export const agenticProjectConfigSchema = z.object({
|
|||
'Optional logo image URL to use for the project. Logos should have a square aspect ratio.'
|
||||
),
|
||||
|
||||
/** Optional URL to the source code of the project. */
|
||||
sourceUrl: z
|
||||
.string()
|
||||
.url()
|
||||
.optional()
|
||||
.describe(
|
||||
'Optional URL to the source code of the project (eg, GitHub repo).'
|
||||
),
|
||||
|
||||
/** Required origin API HTTPS base URL */
|
||||
originUrl: z.string().url()
|
||||
.describe(`Required base URL of the externally hosted origin API server. Must be a valid \`https\` URL.
|
||||
|
@ -97,7 +99,8 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
|||
*
|
||||
* Defaults to a single monthly interval `['month']`.
|
||||
*
|
||||
* To add support for annual pricing plans, you can use `['month', 'year']`.
|
||||
* To add support for annual pricing plans, for example, you can use:
|
||||
* `['month', 'year']`.
|
||||
*
|
||||
* Note that for every pricing interval, you must define a corresponding set
|
||||
* of PricingPlans in the `pricingPlans` array. If you only have one pricing
|
||||
|
@ -106,11 +109,14 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
|||
* specify their `interval` property to differentiate between different
|
||||
* pricing intervals.
|
||||
*/
|
||||
pricingIntervals: z
|
||||
.array(pricingIntervalSchema)
|
||||
.nonempty({
|
||||
message: 'Must contain at least one pricing interval'
|
||||
})
|
||||
pricingIntervals: pricingIntervalListSchema
|
||||
.describe(
|
||||
`Optional list of billing intervals to enable in the pricingPlans.
|
||||
|
||||
Defaults to a single monthly interval \`['month']\`.
|
||||
|
||||
To add support for annual pricing plans, for example, you can use: \`['month', 'year']\`.`
|
||||
)
|
||||
.optional()
|
||||
.default(['month'])
|
||||
})
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
export * from './agentic-project-config-schema'
|
||||
export * from './define-config'
|
||||
export * from './schemas'
|
||||
export * from './utils'
|
||||
export * from './validate-agentic-project-config'
|
||||
export * from './validate-origin-adapter'
|
||||
|
|
|
@ -18,19 +18,33 @@ export const rateLimitSchema = z
|
|||
* The interval at which the rate limit is applied.
|
||||
*
|
||||
* Either a number in seconds or a valid [ms](https://github.com/vercel/ms)
|
||||
* string (eg, `10s`, `1m`, `1h`, `1d`, `1w`, `1y`, etc).
|
||||
* string (eg, "10s", "1m", "1h", "1d", "1w", "1y", etc).
|
||||
*/
|
||||
interval: z.union([
|
||||
z.number().nonnegative(), // seconds
|
||||
z
|
||||
.string()
|
||||
.nonempty()
|
||||
.transform((value, ctx) => {
|
||||
try {
|
||||
// TODO: `ms` module has broken types
|
||||
const ms = parseIntervalAsMs(value as any) as unknown as number
|
||||
interval: z
|
||||
.union([
|
||||
z.number().nonnegative(), // seconds
|
||||
|
||||
if (typeof ms !== 'number' || ms < 0) {
|
||||
z
|
||||
.string()
|
||||
.nonempty()
|
||||
.transform((value, ctx) => {
|
||||
try {
|
||||
// TODO: `ms` module has broken types
|
||||
const ms = parseIntervalAsMs(value as any) as unknown as number
|
||||
|
||||
if (typeof ms !== 'number' || ms < 0) {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid interval "${value}"`,
|
||||
path: ctx.path
|
||||
})
|
||||
|
||||
return z.NEVER
|
||||
}
|
||||
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
return seconds
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid interval "${value}"`,
|
||||
|
@ -39,20 +53,11 @@ export const rateLimitSchema = z
|
|||
|
||||
return z.NEVER
|
||||
}
|
||||
|
||||
const seconds = Math.floor(ms / 1000)
|
||||
return seconds
|
||||
} catch {
|
||||
ctx.addIssue({
|
||||
code: z.ZodIssueCode.custom,
|
||||
message: `Invalid interval "${value}"`,
|
||||
path: ctx.path
|
||||
})
|
||||
|
||||
return z.NEVER
|
||||
}
|
||||
})
|
||||
]),
|
||||
})
|
||||
])
|
||||
.describe(
|
||||
`The interval at which the rate limit is applied. Either a number in seconds or a valid [ms](https://github.com/vercel/ms) string (eg, "10s", "1m", "1h", "1d", "1w", "1y", etc).`
|
||||
),
|
||||
|
||||
/**
|
||||
* Maximum number of operations per interval (unitless).
|
||||
|
@ -99,6 +104,17 @@ export const pricingIntervalSchema = z
|
|||
.openapi('PricingInterval')
|
||||
export type PricingInterval = z.infer<typeof pricingIntervalSchema>
|
||||
|
||||
/**
|
||||
* List of billing intervals for subscriptions.
|
||||
*/
|
||||
export const pricingIntervalListSchema = z
|
||||
.array(pricingIntervalSchema)
|
||||
.nonempty({
|
||||
message: 'Must contain at least one pricing interval'
|
||||
})
|
||||
.describe('List of billing intervals for subscriptions.')
|
||||
.openapi('PricingIntervalList')
|
||||
|
||||
/**
|
||||
* Internal PricingPlanLineItem hash
|
||||
*
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
import type { PricingInterval, PricingPlan, PricingPlanList } from './schemas'
|
||||
|
||||
export function getPricingPlansByInterval({
|
||||
pricingInterval,
|
||||
pricingPlans
|
||||
}: {
|
||||
pricingInterval: PricingInterval
|
||||
pricingPlans: PricingPlanList
|
||||
}): PricingPlan[] {
|
||||
return pricingPlans.filter(
|
||||
(pricingPlan) =>
|
||||
pricingPlan.interval === pricingInterval ||
|
||||
pricingPlan.interval === undefined
|
||||
)
|
||||
}
|
||||
|
||||
const pricingIntervalToLabelMap: Record<PricingInterval, string> = {
|
||||
day: 'daily',
|
||||
week: 'weekly',
|
||||
month: 'monthly',
|
||||
year: 'yearly'
|
||||
}
|
||||
|
||||
export function getLabelForPricingInterval(
|
||||
pricingInterval: PricingInterval
|
||||
): string {
|
||||
return pricingIntervalToLabelMap[pricingInterval]
|
||||
}
|
|
@ -8,6 +8,7 @@ import {
|
|||
type AgenticProjectConfigInput,
|
||||
agenticProjectConfigSchema
|
||||
} from './agentic-project-config-schema'
|
||||
import { getPricingPlansByInterval } from './utils'
|
||||
import { validateOriginAdapter } from './validate-origin-adapter'
|
||||
|
||||
export async function validateAgenticProjectConfig(
|
||||
|
@ -199,6 +200,21 @@ export async function validateAgenticProjectConfig(
|
|||
}
|
||||
}
|
||||
|
||||
// Validate deployment pricing plans to ensure they contain at least one valid
|
||||
// plan per pricing interval configured on the project.
|
||||
for (const pricingInterval of config.pricingIntervals) {
|
||||
const pricingPlansForInterval = getPricingPlansByInterval({
|
||||
pricingInterval,
|
||||
pricingPlans
|
||||
})
|
||||
|
||||
assert(
|
||||
pricingPlansForInterval.length > 0,
|
||||
400,
|
||||
`Invalid pricing config: no pricing plans for pricing interval "${pricingInterval}"`
|
||||
)
|
||||
}
|
||||
|
||||
await validateOriginAdapter({
|
||||
...opts,
|
||||
label: `project "${name}"`,
|
||||
|
|
|
@ -7,17 +7,24 @@
|
|||
|
||||
## TODO
|
||||
|
||||
- end-to-end working examples
|
||||
- raw
|
||||
- openapi
|
||||
- mcp
|
||||
- stripe
|
||||
- re-add coupons
|
||||
- declarative json-based pricing
|
||||
- like https://github.com/tierrun/tier and Saasify
|
||||
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
|
||||
- https://blog.tier.run/tier-hello-world-demo
|
||||
- stripe connect
|
||||
- consider switching to [consola](https://github.com/unjs/consola) for logging?
|
||||
- consider switching to `bun` (for `--hot` reloading!!)
|
||||
- transactional emails
|
||||
- openauth password emails and `sendCode`
|
||||
- re-add support for teams / organizations
|
||||
- api gateway
|
||||
- signed requests
|
||||
|
||||
## License
|
||||
|
||||
|
|
Ładowanie…
Reference in New Issue