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: {
|
partialUser: {
|
||||||
email: ghUser.email,
|
email: ghUser.email,
|
||||||
emailVerified: true,
|
isEmailVerified: true,
|
||||||
name: ghUser.name || undefined,
|
name: ghUser.name || undefined,
|
||||||
username: ghUser.login.toLowerCase(),
|
username: ghUser.login.toLowerCase(),
|
||||||
image: ghUser.avatar_url
|
image: ghUser.avatar_url
|
||||||
|
@ -118,7 +118,7 @@ export const authRouter = issuer({
|
||||||
},
|
},
|
||||||
partialUser: {
|
partialUser: {
|
||||||
email: value.email,
|
email: value.email,
|
||||||
emailVerified: true
|
isEmailVerified: true
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
import {
|
import {
|
||||||
|
agenticProjectConfigSchema,
|
||||||
type DeploymentOriginAdapter,
|
type DeploymentOriginAdapter,
|
||||||
deploymentOriginAdapterSchema,
|
deploymentOriginAdapterSchema,
|
||||||
|
pricingIntervalListSchema,
|
||||||
type PricingPlanList,
|
type PricingPlanList,
|
||||||
pricingPlanListSchema
|
pricingPlanListSchema
|
||||||
} from '@agentic/platform-schemas'
|
} from '@agentic/platform-schemas'
|
||||||
|
@ -29,6 +31,7 @@ import {
|
||||||
createUpdateSchema,
|
createUpdateSchema,
|
||||||
deploymentIdentifier,
|
deploymentIdentifier,
|
||||||
deploymentPrimaryId,
|
deploymentPrimaryId,
|
||||||
|
pricingIntervalEnum,
|
||||||
projectId,
|
projectId,
|
||||||
teamId,
|
teamId,
|
||||||
timestamps,
|
timestamps,
|
||||||
|
@ -65,11 +68,10 @@ export const deployments = pgTable(
|
||||||
onDelete: 'cascade'
|
onDelete: 'cascade'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
// TODO: Tool definitions or OpenAPI spec
|
// TODO: Tool definitions
|
||||||
// services: jsonb().$type<Service[]>().default([]),
|
// tools: jsonb().$type<Tool[]>().default([]),
|
||||||
|
|
||||||
// TODO: metadata config (logo, keywords, examples, etc)
|
// TODO: metadata config (logo, keywords, examples, etc)
|
||||||
// TODO: openapi spec or tool definitions or mcp adapter
|
|
||||||
// TODO: webhooks
|
// TODO: webhooks
|
||||||
// TODO: third-party auth provider config
|
// TODO: third-party auth provider config
|
||||||
// NOTE: will need consumer.authProviders as well as user.authProviders for
|
// NOTE: will need consumer.authProviders as well as user.authProviders for
|
||||||
|
@ -84,7 +86,10 @@ export const deployments = pgTable(
|
||||||
originAdapter: jsonb().$type<DeploymentOriginAdapter>().notNull(),
|
originAdapter: jsonb().$type<DeploymentOriginAdapter>().notNull(),
|
||||||
|
|
||||||
// Array<PricingPlan>
|
// 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()
|
// coupons: jsonb().$type<Coupon[]>().default([]).notNull()
|
||||||
},
|
},
|
||||||
|
@ -135,8 +140,9 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
|
||||||
message: 'Invalid deployment hash'
|
message: 'Invalid deployment hash'
|
||||||
}),
|
}),
|
||||||
|
|
||||||
|
originAdapter: deploymentOriginAdapterSchema,
|
||||||
pricingPlans: pricingPlanListSchema,
|
pricingPlans: pricingPlanListSchema,
|
||||||
originAdapter: deploymentOriginAdapterSchema
|
pricingIntervals: pricingIntervalListSchema
|
||||||
})
|
})
|
||||||
.omit({
|
.omit({
|
||||||
originUrl: true
|
originUrl: true
|
||||||
|
@ -159,33 +165,7 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
|
||||||
.openapi('Deployment')
|
.openapi('Deployment')
|
||||||
|
|
||||||
export const deploymentInsertSchema = createInsertSchema(deployments, {
|
export const deploymentInsertSchema = createInsertSchema(deployments, {
|
||||||
projectId: projectIdSchema,
|
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()
|
|
||||||
})
|
})
|
||||||
.omit({
|
.omit({
|
||||||
id: true,
|
id: true,
|
||||||
|
@ -195,8 +175,15 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
||||||
userId: true,
|
userId: true,
|
||||||
teamId: true
|
teamId: true
|
||||||
})
|
})
|
||||||
|
.extend({
|
||||||
|
...agenticProjectConfigSchema.shape
|
||||||
|
})
|
||||||
.strict()
|
.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)
|
export const deploymentUpdateSchema = createUpdateSchema(deployments)
|
||||||
.pick({
|
.pick({
|
||||||
deletedAt: true,
|
deletedAt: true,
|
||||||
|
|
|
@ -10,7 +10,6 @@ import {
|
||||||
import { validators } from '@agentic/platform-validators'
|
import { validators } from '@agentic/platform-validators'
|
||||||
import { relations } from '@fisch0920/drizzle-orm'
|
import { relations } from '@fisch0920/drizzle-orm'
|
||||||
import {
|
import {
|
||||||
boolean,
|
|
||||||
index,
|
index,
|
||||||
integer,
|
integer,
|
||||||
jsonb,
|
jsonb,
|
||||||
|
@ -18,7 +17,6 @@ import {
|
||||||
text,
|
text,
|
||||||
uniqueIndex
|
uniqueIndex
|
||||||
} from '@fisch0920/drizzle-orm/pg-core'
|
} from '@fisch0920/drizzle-orm/pg-core'
|
||||||
import { z } from '@hono/zod-openapi'
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
deploymentIdSchema,
|
deploymentIdSchema,
|
||||||
|
@ -69,21 +67,18 @@ export const projects = pgTable(
|
||||||
applicationFeePercent: integer().default(20).notNull(),
|
applicationFeePercent: integer().default(20).notNull(),
|
||||||
|
|
||||||
// TODO: This is going to need to vary from dev to prod
|
// TODO: This is going to need to vary from dev to prod
|
||||||
isStripeConnectEnabled: boolean().default(false).notNull(),
|
//isStripeConnectEnabled: boolean().default(false).notNull(),
|
||||||
|
|
||||||
// Which pricing intervals are supported for subscriptions to this project
|
|
||||||
pricingIntervals: pricingIntervalEnum()
|
|
||||||
.array()
|
|
||||||
.default(['month'])
|
|
||||||
.notNull(),
|
|
||||||
|
|
||||||
// Default pricing interval for subscriptions to this project
|
// 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(),
|
defaultPricingInterval: pricingIntervalEnum().default('month').notNull(),
|
||||||
|
|
||||||
// Pricing currency used across all prices and subscriptions to this project
|
// Pricing currency used across all prices and subscriptions to this project
|
||||||
pricingCurrency: pricingCurrencyEnum().default('usd').notNull(),
|
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(),
|
_secret: text().notNull(),
|
||||||
|
|
||||||
// Auth token used to access the platform API on behalf of this project
|
// 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(),
|
applicationFeePercent: (schema) => schema.nonnegative(),
|
||||||
|
|
||||||
pricingIntervals: z.array(pricingIntervalSchema).nonempty(),
|
|
||||||
defaultPricingInterval: pricingIntervalSchema,
|
defaultPricingInterval: pricingIntervalSchema,
|
||||||
|
|
||||||
_stripeProductIdMap: stripeProductIdMapSchema,
|
_stripeProductIdMap: stripeProductIdMapSchema,
|
||||||
|
|
|
@ -19,8 +19,6 @@ import {
|
||||||
userRoleEnum
|
userRoleEnum
|
||||||
} from './common'
|
} from './common'
|
||||||
|
|
||||||
// This table is mostly managed by better-auth.
|
|
||||||
|
|
||||||
export const users = pgTable(
|
export const users = pgTable(
|
||||||
'users',
|
'users',
|
||||||
{
|
{
|
||||||
|
@ -32,10 +30,10 @@ export const users = pgTable(
|
||||||
|
|
||||||
name: text(),
|
name: text(),
|
||||||
email: text().notNull().unique(),
|
email: text().notNull().unique(),
|
||||||
emailVerified: boolean().default(false).notNull(),
|
isEmailVerified: boolean().default(false).notNull(),
|
||||||
image: text(),
|
image: text(),
|
||||||
|
|
||||||
isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
//isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
|
||||||
|
|
||||||
stripeCustomerId: stripeId()
|
stripeCustomerId: stripeId()
|
||||||
},
|
},
|
||||||
|
@ -59,7 +57,7 @@ export const userSelectSchema = createSelectSchema(users)
|
||||||
export const userUpdateSchema = createUpdateSchema(users)
|
export const userUpdateSchema = createUpdateSchema(users)
|
||||||
.pick({
|
.pick({
|
||||||
name: true,
|
name: true,
|
||||||
image: true,
|
image: true
|
||||||
isStripeConnectEnabledByDefault: true
|
//isStripeConnectEnabledByDefault: true
|
||||||
})
|
})
|
||||||
.strict()
|
.strict()
|
||||||
|
|
|
@ -1,8 +1,6 @@
|
||||||
import type {
|
import type {
|
||||||
PricingInterval,
|
|
||||||
PricingPlan,
|
PricingPlan,
|
||||||
PricingPlanLineItem,
|
PricingPlanLineItem
|
||||||
PricingPlanList
|
|
||||||
} from '@agentic/platform-schemas'
|
} from '@agentic/platform-schemas'
|
||||||
import { hashObject } from '@agentic/platform-core'
|
import { hashObject } from '@agentic/platform-core'
|
||||||
|
|
||||||
|
@ -62,30 +60,3 @@ export function getStripePriceIdForPricingPlanLineItem({
|
||||||
|
|
||||||
return project._stripePriceIdMap[pricingPlanLineItemHash]
|
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<
|
partialUser: Simplify<
|
||||||
SetRequired<
|
SetRequired<
|
||||||
Partial<
|
Partial<
|
||||||
Pick<RawUser, 'email' | 'name' | 'username' | 'image' | 'emailVerified'>
|
Pick<
|
||||||
|
RawUser,
|
||||||
|
'email' | 'name' | 'username' | 'image' | 'isEmailVerified'
|
||||||
|
>
|
||||||
>,
|
>,
|
||||||
'email'
|
'email'
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,17 +1,16 @@
|
||||||
import type {
|
|
||||||
PricingPlan,
|
|
||||||
PricingPlanLineItem
|
|
||||||
} from '@agentic/platform-schemas'
|
|
||||||
import type Stripe from 'stripe'
|
import type Stripe from 'stripe'
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
|
import {
|
||||||
|
getLabelForPricingInterval,
|
||||||
|
type PricingPlan,
|
||||||
|
type PricingPlanLineItem
|
||||||
|
} from '@agentic/platform-schemas'
|
||||||
import pAll from 'p-all'
|
import pAll from 'p-all'
|
||||||
|
|
||||||
import {
|
import {
|
||||||
db,
|
db,
|
||||||
eq,
|
eq,
|
||||||
getLabelForPricingInterval,
|
|
||||||
getPricingPlanLineItemHashForStripePrice,
|
getPricingPlanLineItemHashForStripePrice,
|
||||||
getPricingPlansByInterval,
|
|
||||||
type RawDeployment,
|
type RawDeployment,
|
||||||
type RawProject,
|
type RawProject,
|
||||||
schema
|
schema
|
||||||
|
@ -28,8 +27,12 @@ import { stripe } from '@/lib/external/stripe'
|
||||||
* `_stripeMeterIdMap`, and `_stripePriceIdMap` fields of the given `project`.
|
* `_stripeMeterIdMap`, and `_stripePriceIdMap` fields of the given `project`.
|
||||||
*
|
*
|
||||||
* The `project` will be updated in the DB with any changes.
|
* The `project` will be updated in the DB with any changes.
|
||||||
|
*
|
||||||
* The `deployment` is readonly and will not be updated, since all Stripe
|
* 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.
|
* 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({
|
export async function upsertStripePricing({
|
||||||
deployment,
|
deployment,
|
||||||
|
@ -244,25 +247,6 @@ export async function upsertStripePricing({
|
||||||
}
|
}
|
||||||
|
|
||||||
const upserts: Array<() => Promise<void>> = []
|
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 pricingPlan of deployment.pricingPlans) {
|
||||||
for (const pricingPlanLineItem of pricingPlan.lineItems) {
|
for (const pricingPlanLineItem of pricingPlan.lineItems) {
|
||||||
upserts.push(() =>
|
upserts.push(() =>
|
||||||
|
|
|
@ -119,7 +119,7 @@ export async function upsertStripeSubscription(
|
||||||
assert(
|
assert(
|
||||||
priceId,
|
priceId,
|
||||||
500,
|
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
|
// 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
|
updateParams.cancel_at_period_end = true
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
// TODO: Stripe Connect
|
||||||
updateParams.application_fee_percent = project.applicationFeePercent
|
// if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||||
}
|
// updateParams.application_fee_percent = project.applicationFeePercent
|
||||||
|
// }
|
||||||
|
|
||||||
subscription = await stripe.subscriptions.update(
|
subscription = await stripe.subscriptions.update(
|
||||||
consumer._stripeSubscriptionId,
|
consumer._stripeSubscriptionId,
|
||||||
|
@ -274,9 +275,10 @@ export async function upsertStripeSubscription(
|
||||||
createParams.trial_period_days = pricingPlan.trialPeriodDays
|
createParams.trial_period_days = pricingPlan.trialPeriodDays
|
||||||
}
|
}
|
||||||
|
|
||||||
if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
// TODO: Stripe Connect
|
||||||
createParams.application_fee_percent = project.applicationFeePercent
|
// if (project.isStripeConnectEnabled && project.applicationFeePercent > 0) {
|
||||||
}
|
// createParams.application_fee_percent = project.applicationFeePercent
|
||||||
|
// }
|
||||||
|
|
||||||
logger.debug('subscription', action, { items })
|
logger.debug('subscription', action, { items })
|
||||||
subscription = await stripe.subscriptions.create(
|
subscription = await stripe.subscriptions.create(
|
||||||
|
@ -321,7 +323,7 @@ export async function upsertStripeSubscription(
|
||||||
assert(
|
assert(
|
||||||
stripeSubscriptionItem,
|
stripeSubscriptionItem,
|
||||||
500,
|
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] =
|
consumerUpdate._stripeSubscriptionItemIdMap![lineItem.slug] =
|
||||||
|
@ -329,7 +331,7 @@ export async function upsertStripeSubscription(
|
||||||
assert(
|
assert(
|
||||||
consumerUpdate._stripeSubscriptionItemIdMap![lineItem.slug],
|
consumerUpdate._stripeSubscriptionItemIdMap![lineItem.slug],
|
||||||
500,
|
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";
|
role: "user" | "admin";
|
||||||
name?: string;
|
name?: string;
|
||||||
email: string;
|
email: string;
|
||||||
emailVerified: boolean;
|
isEmailVerified: boolean;
|
||||||
image?: string;
|
image?: string;
|
||||||
isStripeConnectEnabledByDefault: boolean;
|
|
||||||
stripeCustomerId?: string;
|
stripeCustomerId?: string;
|
||||||
};
|
};
|
||||||
Team: {
|
Team: {
|
||||||
|
@ -367,8 +366,6 @@ export interface components {
|
||||||
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
||||||
lastDeploymentId?: string;
|
lastDeploymentId?: string;
|
||||||
applicationFeePercent: number;
|
applicationFeePercent: number;
|
||||||
isStripeConnectEnabled: boolean;
|
|
||||||
pricingIntervals: components["schemas"]["PricingInterval"][];
|
|
||||||
defaultPricingInterval: components["schemas"]["PricingInterval"];
|
defaultPricingInterval: components["schemas"]["PricingInterval"];
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
pricingCurrency: "usd";
|
pricingCurrency: "usd";
|
||||||
|
@ -419,7 +416,9 @@ export interface components {
|
||||||
/** @example API calls */
|
/** @example API calls */
|
||||||
label: string;
|
label: string;
|
||||||
RateLimit: {
|
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;
|
maxPerInterval: number;
|
||||||
};
|
};
|
||||||
PricingPlanTier: {
|
PricingPlanTier: {
|
||||||
|
@ -427,17 +426,15 @@ export interface components {
|
||||||
flatAmount?: number;
|
flatAmount?: number;
|
||||||
upTo: number | "inf";
|
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: {
|
PricingPlanLineItem: {
|
||||||
slug: string | "base" | "requests";
|
slug: string | "base" | "requests";
|
||||||
interval?: components["schemas"]["PricingInterval"];
|
|
||||||
label?: components["schemas"]["label"];
|
label?: components["schemas"]["label"];
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
usageType: "licensed";
|
usageType: "licensed";
|
||||||
amount: number;
|
amount: number;
|
||||||
} | {
|
} | {
|
||||||
slug: string | "base" | "requests";
|
slug: string | "base" | "requests";
|
||||||
interval?: components["schemas"]["PricingInterval"];
|
|
||||||
label?: components["schemas"]["label"];
|
label?: components["schemas"]["label"];
|
||||||
/** @enum {string} */
|
/** @enum {string} */
|
||||||
usageType: "metered";
|
usageType: "metered";
|
||||||
|
@ -461,11 +458,13 @@ export interface components {
|
||||||
name: components["schemas"]["name"];
|
name: components["schemas"]["name"];
|
||||||
slug: components["schemas"]["slug"];
|
slug: components["schemas"]["slug"];
|
||||||
interval?: components["schemas"]["PricingInterval"];
|
interval?: components["schemas"]["PricingInterval"];
|
||||||
desc?: string;
|
description?: string;
|
||||||
features?: string[];
|
features?: string[];
|
||||||
trialPeriodDays?: number;
|
trialPeriodDays?: number;
|
||||||
lineItems: components["schemas"]["PricingPlanLineItem"][];
|
lineItems: components["schemas"]["PricingPlanLineItem"][];
|
||||||
};
|
};
|
||||||
|
/** @description List of billing intervals for subscriptions. */
|
||||||
|
PricingIntervalList: components["schemas"]["PricingInterval"][];
|
||||||
Deployment: {
|
Deployment: {
|
||||||
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
|
||||||
id: string;
|
id: string;
|
||||||
|
@ -489,6 +488,7 @@ export interface components {
|
||||||
originAdapter: components["schemas"]["DeploymentOriginAdapter"];
|
originAdapter: components["schemas"]["DeploymentOriginAdapter"];
|
||||||
/** @description List of PricingPlans */
|
/** @description List of PricingPlans */
|
||||||
pricingPlans: components["schemas"]["PricingPlan"][];
|
pricingPlans: components["schemas"]["PricingPlan"][];
|
||||||
|
pricingIntervals: components["schemas"]["PricingIntervalList"];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
responses: {
|
responses: {
|
||||||
|
@ -608,7 +608,6 @@ export interface operations {
|
||||||
"application/json": {
|
"application/json": {
|
||||||
name?: string;
|
name?: string;
|
||||||
image?: string;
|
image?: string;
|
||||||
isStripeConnectEnabledByDefault?: boolean;
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
@ -1270,27 +1269,50 @@ export interface operations {
|
||||||
identifier: string;
|
identifier: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
published?: boolean;
|
published?: boolean;
|
||||||
|
/** @description A short description of the project. */
|
||||||
description?: string;
|
description?: string;
|
||||||
|
/** @description A readme documenting the project (supports GitHub-flavored markdown). */
|
||||||
readme?: string;
|
readme?: string;
|
||||||
/**
|
/**
|
||||||
* Format: uri
|
* 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;
|
iconUrl?: string;
|
||||||
/** Format: uri */
|
/**
|
||||||
|
* Format: uri
|
||||||
|
* @description Optional URL to the source code of the project (eg, GitHub repo).
|
||||||
|
*/
|
||||||
sourceUrl?: string;
|
sourceUrl?: string;
|
||||||
/** @description Project id (e.g. "proj_tz4a98xxat96iws9zmbrgj3a") */
|
/** @description Project id (e.g. "proj_tz4a98xxat96iws9zmbrgj3a") */
|
||||||
projectId: string;
|
projectId: string;
|
||||||
/**
|
/**
|
||||||
* Format: uri
|
* 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.
|
* 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;
|
originUrl: string;
|
||||||
originAdapter?: components["schemas"]["DeploymentOriginAdapter"] & unknown;
|
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 {
|
import {
|
||||||
deploymentOriginAdapterSchema,
|
deploymentOriginAdapterSchema,
|
||||||
pricingIntervalSchema,
|
pricingIntervalListSchema,
|
||||||
type PricingPlan,
|
type PricingPlan,
|
||||||
pricingPlanListSchema
|
pricingPlanListSchema
|
||||||
} from './schemas'
|
} from './schemas'
|
||||||
|
@ -54,13 +54,6 @@ export const agenticProjectConfigSchema = z.object({
|
||||||
)
|
)
|
||||||
.optional(),
|
.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.
|
* 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 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 */
|
/** Required origin API HTTPS base URL */
|
||||||
originUrl: z.string().url()
|
originUrl: z.string().url()
|
||||||
.describe(`Required base URL of the externally hosted origin API server. Must be a valid \`https\` 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']`.
|
* 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
|
* Note that for every pricing interval, you must define a corresponding set
|
||||||
* of PricingPlans in the `pricingPlans` array. If you only have one pricing
|
* 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
|
* specify their `interval` property to differentiate between different
|
||||||
* pricing intervals.
|
* pricing intervals.
|
||||||
*/
|
*/
|
||||||
pricingIntervals: z
|
pricingIntervals: pricingIntervalListSchema
|
||||||
.array(pricingIntervalSchema)
|
.describe(
|
||||||
.nonempty({
|
`Optional list of billing intervals to enable in the pricingPlans.
|
||||||
message: 'Must contain at least one pricing interval'
|
|
||||||
})
|
Defaults to a single monthly interval \`['month']\`.
|
||||||
|
|
||||||
|
To add support for annual pricing plans, for example, you can use: \`['month', 'year']\`.`
|
||||||
|
)
|
||||||
.optional()
|
.optional()
|
||||||
.default(['month'])
|
.default(['month'])
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
export * from './agentic-project-config-schema'
|
export * from './agentic-project-config-schema'
|
||||||
export * from './define-config'
|
export * from './define-config'
|
||||||
export * from './schemas'
|
export * from './schemas'
|
||||||
|
export * from './utils'
|
||||||
export * from './validate-agentic-project-config'
|
export * from './validate-agentic-project-config'
|
||||||
export * from './validate-origin-adapter'
|
export * from './validate-origin-adapter'
|
||||||
|
|
|
@ -18,10 +18,12 @@ export const rateLimitSchema = z
|
||||||
* The interval at which the rate limit is applied.
|
* The interval at which the rate limit is applied.
|
||||||
*
|
*
|
||||||
* Either a number in seconds or a valid [ms](https://github.com/vercel/ms)
|
* 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([
|
interval: z
|
||||||
|
.union([
|
||||||
z.number().nonnegative(), // seconds
|
z.number().nonnegative(), // seconds
|
||||||
|
|
||||||
z
|
z
|
||||||
.string()
|
.string()
|
||||||
.nonempty()
|
.nonempty()
|
||||||
|
@ -52,7 +54,10 @@ export const rateLimitSchema = z
|
||||||
return z.NEVER
|
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).
|
* Maximum number of operations per interval (unitless).
|
||||||
|
@ -99,6 +104,17 @@ export const pricingIntervalSchema = z
|
||||||
.openapi('PricingInterval')
|
.openapi('PricingInterval')
|
||||||
export type PricingInterval = z.infer<typeof pricingIntervalSchema>
|
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
|
* 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,
|
type AgenticProjectConfigInput,
|
||||||
agenticProjectConfigSchema
|
agenticProjectConfigSchema
|
||||||
} from './agentic-project-config-schema'
|
} from './agentic-project-config-schema'
|
||||||
|
import { getPricingPlansByInterval } from './utils'
|
||||||
import { validateOriginAdapter } from './validate-origin-adapter'
|
import { validateOriginAdapter } from './validate-origin-adapter'
|
||||||
|
|
||||||
export async function validateAgenticProjectConfig(
|
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({
|
await validateOriginAdapter({
|
||||||
...opts,
|
...opts,
|
||||||
label: `project "${name}"`,
|
label: `project "${name}"`,
|
||||||
|
|
|
@ -7,17 +7,24 @@
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
|
- end-to-end working examples
|
||||||
|
- raw
|
||||||
|
- openapi
|
||||||
|
- mcp
|
||||||
- stripe
|
- stripe
|
||||||
- re-add coupons
|
- re-add coupons
|
||||||
- declarative json-based pricing
|
- declarative json-based pricing
|
||||||
- like https://github.com/tierrun/tier and Saasify
|
- like https://github.com/tierrun/tier and Saasify
|
||||||
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
|
- https://github.com/tierrun/tier/blob/main/pricing/schema.json
|
||||||
- https://blog.tier.run/tier-hello-world-demo
|
- https://blog.tier.run/tier-hello-world-demo
|
||||||
|
- stripe connect
|
||||||
- consider switching to [consola](https://github.com/unjs/consola) for logging?
|
- consider switching to [consola](https://github.com/unjs/consola) for logging?
|
||||||
- consider switching to `bun` (for `--hot` reloading!!)
|
- consider switching to `bun` (for `--hot` reloading!!)
|
||||||
- transactional emails
|
- transactional emails
|
||||||
- openauth password emails and `sendCode`
|
- openauth password emails and `sendCode`
|
||||||
- re-add support for teams / organizations
|
- re-add support for teams / organizations
|
||||||
|
- api gateway
|
||||||
|
- signed requests
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue