kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
pull/715/head
rodzic
bf7f8f60f4
commit
23364946eb
|
@ -187,7 +187,7 @@ export async function upsertStripePricing({
|
|||
)
|
||||
|
||||
priceParams.tiers_mode = pricingPlanLineItem.tiersMode
|
||||
priceParams.tiers = pricingPlanLineItem.tiers!.map((tierData) => {
|
||||
priceParams.tiers = pricingPlanLineItem.tiers.map((tierData) => {
|
||||
const tier: Stripe.PriceCreateParams.Tier = {
|
||||
up_to: tierData.upTo
|
||||
}
|
||||
|
|
|
@ -1,22 +1,20 @@
|
|||
import { defineConfig, freePricingPlan } from '@agentic/platform-schemas'
|
||||
import { defaultFreePricingPlan, defineConfig } from '@agentic/platform-schemas'
|
||||
|
||||
export default defineConfig({
|
||||
// TODO: resolve name / slug conflicts
|
||||
name: 'My Project',
|
||||
originUrl: 'https://httpbin.org',
|
||||
pricingPlans: [
|
||||
freePricingPlan,
|
||||
defaultFreePricingPlan,
|
||||
{
|
||||
name: 'Basic',
|
||||
slug: 'basic',
|
||||
// interval: 'month',
|
||||
trialPeriodDays: 7,
|
||||
lineItems: [
|
||||
{
|
||||
slug: 'base',
|
||||
usageType: 'licensed',
|
||||
amount: 490
|
||||
// interval: 'month'
|
||||
amount: 499 // $4.99 USD
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
@ -3,21 +3,68 @@
|
|||
exports[`loadAgenticConfig > basic-raw-free-json 1`] = `
|
||||
{
|
||||
"name": "My Project",
|
||||
"originAdapter": {
|
||||
"location": "external",
|
||||
"type": "raw",
|
||||
},
|
||||
"originUrl": "https://jsonplaceholder.typicode.com",
|
||||
"pricingIntervals": [
|
||||
"month",
|
||||
],
|
||||
"pricingPlans": [
|
||||
{
|
||||
"lineItems": [
|
||||
{
|
||||
"amount": 0,
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
],
|
||||
"name": "Free",
|
||||
"slug": "free",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`loadAgenticConfig > basic-raw-free-ts 1`] = `
|
||||
{
|
||||
"name": "My Project",
|
||||
"originAdapter": {
|
||||
"location": "external",
|
||||
"type": "raw",
|
||||
},
|
||||
"originUrl": "https://jsonplaceholder.typicode.com",
|
||||
"pricingIntervals": [
|
||||
"month",
|
||||
],
|
||||
"pricingPlans": [
|
||||
{
|
||||
"lineItems": [
|
||||
{
|
||||
"amount": 0,
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
],
|
||||
"name": "Free",
|
||||
"slug": "free",
|
||||
},
|
||||
],
|
||||
}
|
||||
`;
|
||||
|
||||
exports[`loadAgenticConfig > pricing-freemium 1`] = `
|
||||
{
|
||||
"name": "My Project",
|
||||
"originAdapter": {
|
||||
"location": "external",
|
||||
"type": "raw",
|
||||
},
|
||||
"originUrl": "https://httpbin.org",
|
||||
"pricingIntervals": [
|
||||
"month",
|
||||
],
|
||||
"pricingPlans": [
|
||||
{
|
||||
"lineItems": [
|
||||
|
@ -34,7 +81,7 @@ exports[`loadAgenticConfig > pricing-freemium 1`] = `
|
|||
"interval": "month",
|
||||
"lineItems": [
|
||||
{
|
||||
"amount": 490,
|
||||
"amount": 499,
|
||||
"interval": "month",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
|
|
|
@ -1,10 +1,8 @@
|
|||
import { parseZodSchema } from '@agentic/platform-core'
|
||||
import {
|
||||
type AgenticProjectConfigOutput,
|
||||
agenticProjectConfigSchema
|
||||
} from '@agentic/platform-schemas'
|
||||
import type { AgenticProjectConfigOutput } from '@agentic/platform-schemas'
|
||||
import { loadConfig } from 'unconfig'
|
||||
|
||||
import { validateAgenticConfig } from './validate-agentic-config'
|
||||
|
||||
export async function loadAgenticConfig({
|
||||
cwd
|
||||
}: {
|
||||
|
@ -20,5 +18,5 @@ export async function loadAgenticConfig({
|
|||
]
|
||||
})
|
||||
|
||||
return parseZodSchema(agenticProjectConfigSchema, config)
|
||||
return validateAgenticConfig(config)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,224 @@
|
|||
import type { ZodTypeDef } from 'zod'
|
||||
import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
import {
|
||||
type AgenticProjectConfigInput,
|
||||
type AgenticProjectConfigOutput,
|
||||
agenticProjectConfigSchema,
|
||||
type PricingPlanLineItem
|
||||
} from '@agentic/platform-schemas'
|
||||
|
||||
export async function validateAgenticConfig(
|
||||
inputConfig: unknown
|
||||
): Promise<AgenticProjectConfigOutput> {
|
||||
const config = parseZodSchema<
|
||||
AgenticProjectConfigOutput,
|
||||
ZodTypeDef,
|
||||
AgenticProjectConfigInput
|
||||
>(agenticProjectConfigSchema, inputConfig)
|
||||
|
||||
console.log('config', config)
|
||||
|
||||
const { pricingIntervals, pricingPlans } = config
|
||||
assert(
|
||||
pricingPlans?.length,
|
||||
400,
|
||||
'Invalid pricingPlans: must be a non-empty array'
|
||||
)
|
||||
assert(
|
||||
pricingIntervals?.length,
|
||||
400,
|
||||
'Invalid pricingIntervals: must be a non-empty array'
|
||||
)
|
||||
|
||||
{
|
||||
// Validate pricing interval
|
||||
const pricingIntervalsSet = new Set(pricingIntervals)
|
||||
assert(
|
||||
pricingIntervalsSet.size === pricingIntervals.length,
|
||||
400,
|
||||
'Invalid pricingIntervals: duplicate pricing intervals'
|
||||
)
|
||||
assert(
|
||||
pricingIntervals.length >= 1,
|
||||
400,
|
||||
'Invalid pricingIntervals: must contain at least one pricing interval'
|
||||
)
|
||||
|
||||
if (pricingIntervals.length > 1) {
|
||||
for (const pricingPlan of pricingPlans) {
|
||||
if (pricingPlan.interval) {
|
||||
assert(
|
||||
pricingIntervalsSet.has(pricingPlan.interval),
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": PricingPlan "${pricingPlan.slug}" has invalid interval "${pricingPlan.interval}" which is not included in the "pricingIntervals" array.`
|
||||
)
|
||||
}
|
||||
|
||||
if (pricingPlan.slug === 'free') continue
|
||||
|
||||
assert(
|
||||
pricingPlan.interval !== undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": non-free PricingPlan "${pricingPlan.slug}" must specify an "interval" because the project supports multiple pricing intervals.`
|
||||
)
|
||||
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
lineItem.interval ??= pricingPlan.interval
|
||||
|
||||
assert(
|
||||
lineItem.interval === pricingPlan.interval,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": non-free PricingPlan "${pricingPlan.slug}" LineItem "${lineItem.slug}" "interval" must match the PricingPlan interval "${pricingPlan.interval}" because the project supports multiple pricing intervals.`
|
||||
)
|
||||
|
||||
assert(
|
||||
pricingIntervalsSet.has(lineItem.interval),
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": PricingPlan "${pricingPlan.slug}" LineItem "${lineItem.slug}" has invalid interval "${pricingPlan.interval}" which is not included in the "pricingIntervals" array.`
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Only a single pricing interval is supported, so default all pricing
|
||||
// plans to use the default pricing interval.
|
||||
const defaultPricingInterval = pricingIntervals[0]!
|
||||
assert(
|
||||
defaultPricingInterval,
|
||||
400,
|
||||
'Invalid pricingIntervals: must contain at least one valid pricing interval'
|
||||
)
|
||||
|
||||
for (const pricingPlan of pricingPlans) {
|
||||
if (pricingPlan.interval) {
|
||||
assert(
|
||||
pricingIntervalsSet.has(pricingPlan.interval),
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": PricingPlan "${pricingPlan.slug}" has invalid interval "${pricingPlan.interval}" which is not included in the "pricingIntervals" array.`
|
||||
)
|
||||
}
|
||||
|
||||
if (pricingPlan.slug === 'free') continue
|
||||
|
||||
pricingPlan.interval ??= defaultPricingInterval
|
||||
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
lineItem.interval ??= defaultPricingInterval
|
||||
|
||||
assert(
|
||||
pricingIntervalsSet.has(lineItem.interval),
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": PricingPlan "${pricingPlan.slug}" LineItem "${lineItem.slug}" has invalid interval "${pricingPlan.interval}" which is not included in the "pricingIntervals" array.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// Validate pricingPlans
|
||||
const pricingPlanSlugsSet = new Set(pricingPlans.map((p) => p.slug))
|
||||
assert(
|
||||
pricingPlanSlugsSet.size === pricingPlans.length,
|
||||
400,
|
||||
'Invalid pricingPlans: duplicate PricingPlan slugs. All PricingPlan slugs must be unique (e.g. "free", "starter-monthly", "pro-annual", etc).'
|
||||
)
|
||||
|
||||
const pricingPlanLineItemSlugMap: Record<string, PricingPlanLineItem[]> = {}
|
||||
|
||||
for (const pricingPlan of pricingPlans) {
|
||||
const lineItemSlugsSet = new Set(
|
||||
pricingPlan.lineItems.map((lineItem) => lineItem.slug)
|
||||
)
|
||||
|
||||
assert(
|
||||
lineItemSlugsSet.size === pricingPlan.lineItems.length,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": duplicate line-item slugs`
|
||||
)
|
||||
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
if (!pricingPlanLineItemSlugMap[lineItem.slug]) {
|
||||
pricingPlanLineItemSlugMap[lineItem.slug] = []
|
||||
}
|
||||
|
||||
pricingPlanLineItemSlugMap[lineItem.slug]!.push(lineItem)
|
||||
}
|
||||
}
|
||||
|
||||
for (const lineItems of Object.values(pricingPlanLineItemSlugMap)) {
|
||||
if (lineItems.length <= 1) continue
|
||||
|
||||
const lineItem0 = lineItems[0]!
|
||||
|
||||
for (let i = 1; i < lineItems.length; ++i) {
|
||||
const lineItem = lineItems[i]!
|
||||
|
||||
assert(
|
||||
lineItem.usageType === lineItem0.usageType,
|
||||
400,
|
||||
`Invalid pricingPlans: all PricingPlans which contain the same LineItems (by slug "${lineItem.slug}") must have the same usage type ("licensed" or "metered").`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Validate PricingPlanLineItems
|
||||
for (const pricingPlan of pricingPlans) {
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
if (lineItem.usageType === 'metered') {
|
||||
switch (lineItem.billingScheme) {
|
||||
case 'per_unit':
|
||||
assert(
|
||||
lineItem.unitAmount !== undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must specify a non-negative "unitAmount" when using "per_unit" billing scheme.`
|
||||
)
|
||||
|
||||
assert(
|
||||
lineItem.tiersMode === undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must not specify "tiersMode" when using "per_unit" billing scheme.`
|
||||
)
|
||||
|
||||
assert(
|
||||
lineItem.tiers === undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must not specify "tiers" when using "per_unit" billing scheme.`
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
case 'tiered':
|
||||
assert(
|
||||
lineItem.unitAmount === undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must not specify "unitAmount" when using "tiered" billing scheme.`
|
||||
)
|
||||
|
||||
assert(
|
||||
lineItem.tiers?.length,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must specify a non-empty "tiers" array when using "tiered" billing scheme.`
|
||||
)
|
||||
|
||||
assert(
|
||||
lineItem.transformQuantity === undefined,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must not specify "transformQuantity" when using "tiered" billing scheme.`
|
||||
)
|
||||
|
||||
break
|
||||
|
||||
default:
|
||||
assert(
|
||||
false,
|
||||
400,
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": metered LineItem "${lineItem.slug}" must specify a valid "billingScheme".`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return config
|
||||
}
|
|
@ -17,7 +17,7 @@ import {
|
|||
// - optional version
|
||||
// - optional agentic version
|
||||
|
||||
export const freePricingPlan = {
|
||||
export const defaultFreePricingPlan = {
|
||||
name: 'Free',
|
||||
slug: 'free',
|
||||
lineItems: [
|
||||
|
@ -70,30 +70,36 @@ export const agenticProjectConfigSchema = z.object({
|
|||
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.`),
|
||||
|
||||
/** Optional origin API config */
|
||||
originAdapter: deploymentOriginAdapterSchema
|
||||
.default({
|
||||
location: 'external',
|
||||
type: 'raw'
|
||||
})
|
||||
.optional(),
|
||||
originAdapter: deploymentOriginAdapterSchema.optional().default({
|
||||
location: 'external',
|
||||
type: 'raw'
|
||||
}),
|
||||
|
||||
/** Optional subscription pricing config */
|
||||
pricingPlans: pricingPlanListSchema
|
||||
.describe(
|
||||
'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([freePricingPlan])
|
||||
.optional(),
|
||||
.optional()
|
||||
.default([defaultFreePricingPlan]),
|
||||
|
||||
/**
|
||||
* Optional list of billing intervals to enable in the pricingPlans.
|
||||
*
|
||||
* Defaults to a single monthly interval `['month']`.
|
||||
*
|
||||
* To add an annual plan, you can use `['month', 'year']`.
|
||||
* To add support for annual pricing plans, 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
|
||||
* interval (like the default `month` interval), `pricingPlans` don't need to
|
||||
* specify their `interval` property. Otherwise, all PricingPlans and
|
||||
* LineItems must specify their `interval` property to differentiate between
|
||||
* different pricing intervals.
|
||||
*/
|
||||
pricingIntervals: z.array(pricingIntervalSchema).default(['month']).optional()
|
||||
pricingIntervals: z.array(pricingIntervalSchema).optional().default(['month'])
|
||||
})
|
||||
|
||||
export type AgenticProjectConfigInput = z.input<
|
||||
typeof agenticProjectConfigSchema
|
||||
>
|
||||
|
|
|
@ -1,4 +1,3 @@
|
|||
import { parseJson } from '@agentic/platform-core'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
export const webhookSchema = z
|
||||
|
@ -10,7 +9,7 @@ export const webhookSchema = z
|
|||
export type Webhook = z.infer<typeof webhookSchema>
|
||||
|
||||
/**
|
||||
* Rate limit config for metered line-items.
|
||||
* Rate limit config for metered LineItems.
|
||||
*/
|
||||
export const rateLimitSchema = z
|
||||
.object({
|
||||
|
@ -64,7 +63,9 @@ export const pricingPlanLineItemHashSchema = z
|
|||
.describe('Internal PricingPlanLineItem hash')
|
||||
|
||||
/**
|
||||
* PricingPlanLineItem slug which acts as a unique lookup key for LineItems across deployments. They must be lower and kebab-cased ("base", "requests", "image-transformations").
|
||||
* PricingPlanLineItem slug which acts as a unique lookup key for LineItems
|
||||
* across deployments. They must be lower and kebab-cased ("base", "requests",
|
||||
* "image-transformations", etc).
|
||||
*/
|
||||
export const pricingPlanLineItemSlugSchema = z
|
||||
.string()
|
||||
|
@ -111,7 +112,7 @@ const commonPricingPlanLineItemSchema = z.object({
|
|||
* The `requests` slug is reserved for charging using `metered billing based
|
||||
* on the number of request made during a given billing interval.
|
||||
*
|
||||
* All other PricingPlanLineItem `slugs` are considered custom line-items.
|
||||
* All other PricingPlanLineItem `slugs` are considered custom LineItems.
|
||||
*/
|
||||
slug: z.union([z.string(), z.literal('base'), z.literal('requests')]),
|
||||
|
||||
|
@ -123,6 +124,11 @@ const commonPricingPlanLineItemSchema = z.object({
|
|||
*/
|
||||
interval: pricingIntervalSchema.optional(),
|
||||
|
||||
/**
|
||||
* Optional label for the line-item which will be displayed on customer bills.
|
||||
*
|
||||
* If unset, the line-item's `slug` will be used as the label.
|
||||
*/
|
||||
label: z.string().optional().openapi('label', { example: 'API calls' })
|
||||
})
|
||||
|
||||
|
@ -136,6 +142,9 @@ export const pricingPlanLineItemSchema = z
|
|||
.discriminatedUnion('usageType', [
|
||||
commonPricingPlanLineItemSchema.merge(
|
||||
z.object({
|
||||
/**
|
||||
* Licensed LineItems are used to charge for fixed-price services.
|
||||
*/
|
||||
usageType: z.literal('licensed'),
|
||||
|
||||
/**
|
||||
|
@ -151,11 +160,21 @@ export const pricingPlanLineItemSchema = z
|
|||
|
||||
commonPricingPlanLineItemSchema.merge(
|
||||
z.object({
|
||||
/**
|
||||
* Metered LineItems are used to charge for usage-based services.
|
||||
*/
|
||||
usageType: z.literal('metered'),
|
||||
|
||||
/**
|
||||
* Optional label for the line-item which will be displayed on customer
|
||||
* bills.
|
||||
*
|
||||
* If unset, the line-item's `slug` will be used as the unit label.
|
||||
*/
|
||||
unitLabel: z.string().optional(),
|
||||
|
||||
/**
|
||||
* Optional rate limit to enforce for this metered LineItem.
|
||||
* Optional rate limit to enforce for this metered line-item.
|
||||
*
|
||||
* You can use this, for example, to limit the number of API calls that
|
||||
* can be made during a given interval.
|
||||
|
@ -175,7 +194,6 @@ export const pricingPlanLineItemSchema = z
|
|||
*/
|
||||
billingScheme: z.union([z.literal('per_unit'), z.literal('tiered')]),
|
||||
|
||||
//
|
||||
/**
|
||||
* The fixed amount to charge per unit of usage.
|
||||
*
|
||||
|
@ -225,6 +243,8 @@ export const pricingPlanLineItemSchema = z
|
|||
.object({
|
||||
/**
|
||||
* Divide usage by this number.
|
||||
*
|
||||
* Must be a positive number.
|
||||
*/
|
||||
divideBy: z.number().positive(),
|
||||
|
||||
|
@ -246,7 +266,7 @@ export const pricingPlanLineItemSchema = z
|
|||
return true
|
||||
},
|
||||
(data) => ({
|
||||
message: `Invalid PricingPlanLineItem "${data.slug}": reserved "base" line-items must have "licensed" usage type.`
|
||||
message: `Invalid PricingPlanLineItem "${data.slug}": reserved "base" LineItems must have "licensed" usage type.`
|
||||
})
|
||||
)
|
||||
.refine(
|
||||
|
@ -258,18 +278,18 @@ export const pricingPlanLineItemSchema = z
|
|||
return true
|
||||
},
|
||||
(data) => ({
|
||||
message: `Invalid PricingPlanLineItem "${data.slug}": reserved "requests" line-items must have "metered" usage type.`
|
||||
message: `Invalid PricingPlanLineItem "${data.slug}": reserved "requests" LineItems must have "metered" usage type.`
|
||||
})
|
||||
)
|
||||
.describe(
|
||||
'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.'
|
||||
'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.'
|
||||
)
|
||||
.openapi('PricingPlanLineItem')
|
||||
export type PricingPlanLineItem = z.infer<typeof pricingPlanLineItemSchema>
|
||||
|
||||
/**
|
||||
* Represents the config for a Stripe subscription with one or more
|
||||
* PricingPlanLineItems.
|
||||
* Represents the config for a single Stripe subscription plan with one or more
|
||||
* LineItems.
|
||||
*/
|
||||
export const pricingPlanSchema = z
|
||||
.object({
|
||||
|
@ -287,53 +307,32 @@ export const pricingPlanSchema = z
|
|||
*/
|
||||
interval: pricingIntervalSchema.optional(),
|
||||
|
||||
desc: z.string().optional(),
|
||||
/**
|
||||
* Optional description of the PricingPlan which is used for UI-only.
|
||||
*/
|
||||
description: z.string().optional(),
|
||||
|
||||
/**
|
||||
* Optional list of features of the PricingPlan which is used for UI-only.
|
||||
*/
|
||||
features: z.array(z.string()).optional(),
|
||||
|
||||
// TODO?
|
||||
/**
|
||||
* Optional number of days for a free trial period when a customer signs up
|
||||
* for a new subscription.
|
||||
*/
|
||||
trialPeriodDays: z.number().nonnegative().optional(),
|
||||
|
||||
/**
|
||||
* List of LineItems which are included in the PricingPlan.
|
||||
*
|
||||
* Note: we currently support a max of 20 LineItems per plan.
|
||||
*/
|
||||
lineItems: z.array(pricingPlanLineItemSchema).nonempty().max(20, {
|
||||
message:
|
||||
'Stripe Checkout currently supports a max of 20 line-items per subscription.'
|
||||
'Stripe Checkout currently supports a max of 20 LineItems per subscription.'
|
||||
})
|
||||
})
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.interval === undefined) {
|
||||
return data.slug === 'free'
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
(data) => ({
|
||||
message: `Invalid PricingPlan "${data.slug}": non-free pricing plans must have a valid interval`
|
||||
})
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
if (data.slug === 'free') {
|
||||
return data.interval === undefined
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
(data) => ({
|
||||
message: `Invalid PricingPlan "${data.slug}": free pricing plans must not have an interval`
|
||||
})
|
||||
)
|
||||
.refine(
|
||||
(data) => {
|
||||
const lineItemSlugs = new Set(
|
||||
data.lineItems.map((lineItem) => lineItem.slug)
|
||||
)
|
||||
|
||||
return lineItemSlugs.size === data.lineItems.length
|
||||
},
|
||||
(data) => ({
|
||||
message: `Invalid PricingPlan "${data.slug}": duplicate line-item slugs`
|
||||
})
|
||||
)
|
||||
.describe(
|
||||
'Represents the config for a Stripe subscription with one or more PricingPlanLineItems.'
|
||||
)
|
||||
|
@ -360,49 +359,6 @@ export const pricingPlanListSchema = z
|
|||
.nonempty({
|
||||
message: 'Must contain at least one PricingPlan'
|
||||
})
|
||||
.refine(
|
||||
(pricingPlans) => {
|
||||
const slugs = new Set(pricingPlans.map((p) => p.slug))
|
||||
return slugs.size === pricingPlans.length
|
||||
},
|
||||
{
|
||||
message: `Invalid PricingPlanList: duplicate PricingPlan slugs`
|
||||
}
|
||||
)
|
||||
.refine(
|
||||
(pricingPlans) => {
|
||||
const pricingPlanLineItemSlugMap: Record<string, PricingPlanLineItem[]> =
|
||||
{}
|
||||
for (const pricingPlan of pricingPlans) {
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
if (!pricingPlanLineItemSlugMap[lineItem.slug]) {
|
||||
pricingPlanLineItemSlugMap[lineItem.slug] = []
|
||||
}
|
||||
|
||||
pricingPlanLineItemSlugMap[lineItem.slug]!.push(lineItem)
|
||||
}
|
||||
}
|
||||
|
||||
for (const lineItems of Object.values(pricingPlanLineItemSlugMap)) {
|
||||
if (lineItems.length <= 1) continue
|
||||
|
||||
const lineItem0 = lineItems[0]!
|
||||
|
||||
for (let i = 1; i < lineItems.length; ++i) {
|
||||
const lineItem = lineItems[i]!
|
||||
|
||||
if (lineItem.usageType !== lineItem0.usageType) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
},
|
||||
{
|
||||
message: `Invalid PricingPlanList: all pricing plans which contain the same LineItems (by slug) must have the same usage type (licensed or metered).`
|
||||
}
|
||||
)
|
||||
.describe('List of PricingPlans')
|
||||
export type PricingPlanList = z.infer<typeof pricingPlanListSchema>
|
||||
|
||||
|
@ -487,34 +443,20 @@ export const deploymentOriginAdapterSchema = z
|
|||
.discriminatedUnion('type', [
|
||||
z
|
||||
.object({
|
||||
/**
|
||||
* OpenAPI 3.x spec describing the origin API server.
|
||||
*/
|
||||
type: z.literal('openapi'),
|
||||
// NOTE: The origin API servers should be hidden in the embedded
|
||||
// OpenAPI spec, because clients should only be aware of the upstream
|
||||
// Agentic API gateway.
|
||||
|
||||
/**
|
||||
* JSON stringified OpenAPI spec describing the origin API server.
|
||||
*
|
||||
* The origin API servers are be hidden in the embedded OpenAPI spec,
|
||||
* because clients should only be aware of the upstream Agentic API
|
||||
* gateway.
|
||||
*/
|
||||
spec: z
|
||||
.string()
|
||||
.refine(
|
||||
(spec) => {
|
||||
try {
|
||||
parseJson(spec)
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
},
|
||||
(data) => {
|
||||
try {
|
||||
parseJson(data)
|
||||
} catch (err: any) {
|
||||
return {
|
||||
message: `Invalid OpenAPI spec: ${err.message}`
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
message: 'Invalid OpenAPI spec'
|
||||
}
|
||||
}
|
||||
)
|
||||
.describe(
|
||||
'JSON stringified OpenAPI spec describing the origin API server.'
|
||||
)
|
||||
|
@ -523,6 +465,13 @@ export const deploymentOriginAdapterSchema = z
|
|||
|
||||
z
|
||||
.object({
|
||||
/**
|
||||
* Marks the origin server as a raw HTTP REST API without any additional
|
||||
* tool or service definitions.
|
||||
*
|
||||
* In this mode, Agentic's API gateway acts as a simple reverse-proxy
|
||||
* to the origin server, without validating tools or services.
|
||||
*/
|
||||
type: z.literal('raw')
|
||||
})
|
||||
.merge(commonDeploymentOriginAdapterSchema)
|
||||
|
|
Ładowanie…
Reference in New Issue