pull/715/head
Travis Fischer 2025-05-26 17:13:03 +07:00
rodzic 9a0fabd498
commit cdf55f672f
6 zmienionych plików z 148 dodań i 93 usunięć

Wyświetl plik

@ -1,5 +1,5 @@
import { assert, parseZodSchema, pick, sha256 } from '@agentic/platform-core' import { assert, parseZodSchema, sha256 } from '@agentic/platform-core'
import { validateOriginAdapter } from '@agentic/platform-schemas' import { validateAgenticProjectConfig } from '@agentic/platform-schemas'
import { validators } from '@agentic/platform-validators' import { validators } from '@agentic/platform-validators'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi' import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
@ -99,10 +99,15 @@ export function registerV1DeploymentsCreateDeployment(
}) })
} }
// Validate origin config, including any OpenAPI or MCP specs // Validate project config, including:
await validateOriginAdapter({ // - pricing plans
...pick(body, 'originUrl', 'originAdapter'), // - origin adapter config
// - origin API base UrL
// - origin adapter OpenAPI or MCP specs
// - tool definitions
const agenticProjectConfig = await validateAgenticProjectConfig(body, {
label: `deployment "${deploymentIdentifier}"`, label: `deployment "${deploymentIdentifier}"`,
strip: true,
logger logger
}) })
@ -111,6 +116,7 @@ export function registerV1DeploymentsCreateDeployment(
.insert(schema.deployments) .insert(schema.deployments)
.values({ .values({
...body, ...body,
...agenticProjectConfig,
identifier: deploymentIdentifier, identifier: deploymentIdentifier,
hash, hash,
userId: user.id, userId: user.id,
@ -139,8 +145,6 @@ export function registerV1DeploymentsCreateDeployment(
version: deployment.version! version: deployment.version!
}) })
} }
// TODO: validate deployment originUrl, originAdapter, originSchema, and
// originSchemaVersion
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment)) return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
}) })

Wyświetl plik

@ -38,14 +38,13 @@ import { users } from './user'
// https://docs.rapidapi.com/docs/keys#section-different-api-keys-per-application // https://docs.rapidapi.com/docs/keys#section-different-api-keys-per-application
/** /**
* A `Consumer` is a user who has subscribed to a `Project`. * A `Consumer` represents a user who has subscribed to a `Project` and is used
* * to track usage and billing.
* Consumers are used to track usage and billing for a project.
* *
* Consumers are linked to a corresponding Stripe Customer and Subscription. * Consumers are linked to a corresponding Stripe Customer and Subscription.
* The Stripe customer will either be the user's default Stripe Customer for * The Stripe customer will either be the user's default Stripe Customer if the
* the platform account, or a customer on the project's connected Stripe * project uses the default Agentic platform account, or a customer on the project
* account if the project has Stripe Connect enabled. * owner's connected Stripe account if the project has Stripe Connect enabled.
*/ */
export const consumers = pgTable( export const consumers = pgTable(
'consumers', 'consumers',
@ -168,6 +167,15 @@ export const consumerSelectSchema = createSelectSchema(consumers, {
// .openapi('Deployment', { type: 'object' }) // .openapi('Deployment', { type: 'object' })
// }) // })
.strip() .strip()
.describe(
`A Consumer represents a user who has subscribed to a Project and is used
to track usage and billing.
Consumers are linked to a corresponding Stripe Customer and Subscription.
The Stripe customer will either be the user's default Stripe Customer if the
project uses the default Agentic platform account, or a customer on the project
owner's connected Stripe account if the project has Stripe Connect enabled.`
)
.openapi('Consumer') .openapi('Consumer')
export const consumerInsertSchema = createInsertSchema(consumers, { export const consumerInsertSchema = createInsertSchema(consumers, {

Wyświetl plik

@ -41,6 +41,15 @@ import { projects } from './project'
import { teams } from './team' import { teams } from './team'
import { users } from './user' import { users } from './user'
/**
* A Deployment is a single, immutable instance of a Project. Each deployment
* contains pricing plans, origin server config (OpenAPI or MCP server), tool
* definitions, and metadata.
*
* Deployments are private to a developer or team until they are published, at
* which point they are accessible to any customers with access to the parent
* Project.
*/
export const deployments = pgTable( export const deployments = pgTable(
'deployments', 'deployments',
{ {
@ -162,6 +171,11 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
// project: z.object({}).optional().openapi('Project', { type: 'object' }) // project: z.object({}).optional().openapi('Project', { type: 'object' })
// }) // })
.strip() .strip()
.describe(
`A Deployment is a single, immutable instance of a Project. Each deployment contains pricing plans, origin server config (OpenAPI or MCP server), tool definitions, and metadata.
Deployments are private to a developer or team until they are published, at which point they are accessible to any customers with access to the parent Project.`
)
.openapi('Deployment') .openapi('Deployment')
export const deploymentInsertSchema = createInsertSchema(deployments, { export const deploymentInsertSchema = createInsertSchema(deployments, {

Wyświetl plik

@ -43,6 +43,18 @@ import { deployments } from './deployment'
import { teams } from './team' import { teams } from './team'
import { users } from './user' import { users } from './user'
/**
* A Project represents a single Agentic API product. A Project is comprised of
* a series of immutable Deployments, each of which contains pricing data, origin
* API config, OpenAPI or MCP specs, tool definitions, and various metadata.
*
* You can think of Agentic Projects as similar to Vercel projects. They both
* hold some common configuration and are comprised of a series of immutable
* Deployments.
*
* Internally, Projects manage all of the Stripe billing resources across
* Deployments (Stripe Products, Prices, and Meters for usage-based billing).
*/
export const projects = pgTable( export const projects = pgTable(
'projects', 'projects',
{ {
@ -210,6 +222,13 @@ export const projectSelectSchema = createSelectSchema(projects, {
// .openapi('Deployment', { type: 'object' }) // .openapi('Deployment', { type: 'object' })
// }) // })
.strip() .strip()
.describe(
`A Project represents a single Agentic API product. A Project is comprised of a series of immutable Deployments, each of which contains pricing data, origin API config, OpenAPI or MCP specs, tool definitions, and various metadata.
You can think of Agentic Projects as similar to Vercel projects. They both hold some common configuration and are comprised of a series of immutable Deployments.
Internally, Projects manage all of the Stripe billing resources across Deployments (Stripe Products, Prices, and Meters for usage-based billing).`
)
.openapi('Project') .openapi('Project')
export const projectInsertSchema = createInsertSchema(projects, { export const projectInsertSchema = createInsertSchema(projects, {

Wyświetl plik

@ -29,97 +29,99 @@ export const defaultFreePricingPlan = {
] ]
} as const satisfies Readonly<PricingPlan> } as const satisfies Readonly<PricingPlan>
export const agenticProjectConfigSchema = z.object({ export const agenticProjectConfigSchema = z
/** .object({
* Required name of the project. /**
* * Required name of the project.
* Must be lower kebab-case with no spaces and between 2 and 64 characters. *
* * Must be lower kebab-case with no spaces and between 2 and 64 characters.
* @example "my-project" *
* @example "linkedin-resolver-23" * @example "my-project"
*/ * @example "linkedin-resolver-23"
name: z.string().nonempty().describe('Name of the project.'), */
name: z.string().nonempty().describe('Name of the project.'),
/** Optional short description of the project. */ /** Optional short description of the project. */
description: z description: z
.string() .string()
.describe('A short description of the project.') .describe('A short description of the project.')
.optional(), .optional(),
/** Optional readme documenting the project (supports GitHub-flavored markdown). */ /** Optional readme documenting the project (supports GitHub-flavored markdown). */
readme: z readme: z
.string() .string()
.describe( .describe(
'A readme documenting the project (supports GitHub-flavored markdown).' 'A readme documenting the project (supports GitHub-flavored markdown).'
) )
.optional(), .optional(),
/** /**
* 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.
*/ */
iconUrl: z iconUrl: z
.string() .string()
.url() .url()
.optional() .optional()
.describe( .describe(
'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. */ /** Optional URL to the source code of the project. */
sourceUrl: z sourceUrl: z
.string() .string()
.url() .url()
.optional() .optional()
.describe( .describe(
'Optional URL to the source code of the project (eg, GitHub repo).' '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.
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.`),
/** Optional origin API config */ /** Optional origin API config */
originAdapter: deploymentOriginAdapterSchema.optional().default({ originAdapter: deploymentOriginAdapterSchema.optional().default({
location: 'external', location: 'external',
type: 'raw' type: 'raw'
}), }),
/** Optional subscription pricing config */ /** Optional subscription pricing config */
pricingPlans: pricingPlanListSchema pricingPlans: pricingPlanListSchema
.describe( .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.' '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.'
) )
.optional() .optional()
.default([defaultFreePricingPlan]), .default([defaultFreePricingPlan]),
/** /**
* Optional list of billing intervals to enable in the pricingPlans. * Optional list of billing intervals to enable in the pricingPlans.
* *
* Defaults to a single monthly interval `['month']`. * Defaults to a single monthly interval `['month']`.
* *
* To add support for annual pricing plans, for example, you can use: * To add support for annual pricing plans, for example, you can use:
* `['month', 'year']`. * `['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
* interval (like the default `month` interval), `pricingPlans` don't need to * interval (like the default `month` interval), `pricingPlans` don't need to
* specify their `interval` property. Otherwise, all PricingPlans must * specify their `interval` property. Otherwise, all PricingPlans must
* specify their `interval` property to differentiate between different * specify their `interval` property to differentiate between different
* pricing intervals. * pricing intervals.
*/ */
pricingIntervals: pricingIntervalListSchema pricingIntervals: pricingIntervalListSchema
.describe( .describe(
`Optional list of billing intervals to enable in the pricingPlans. `Optional list of billing intervals to enable in the pricingPlans.
Defaults to a single monthly interval \`['month']\`. Defaults to a single monthly interval \`['month']\`.
To add support for annual pricing plans, for example, you can use: \`['month', 'year']\`.` To add support for annual pricing plans, for example, you can use: \`['month', 'year']\`.`
) )
.optional() .optional()
.default(['month']) .default(['month'])
}) })
.strip()
export type AgenticProjectConfigInput = z.input< export type AgenticProjectConfigInput = z.input<
typeof agenticProjectConfigSchema typeof agenticProjectConfigSchema

Wyświetl plik

@ -13,13 +13,21 @@ import { validateOriginAdapter } from './validate-origin-adapter'
export async function validateAgenticProjectConfig( export async function validateAgenticProjectConfig(
inputConfig: unknown, inputConfig: unknown,
opts: { logger?: Logger; cwd?: URL } = {} {
strip = false,
...opts
}: { logger?: Logger; cwd?: URL; strip?: boolean; label?: string } = {}
): Promise<AgenticProjectConfig> { ): Promise<AgenticProjectConfig> {
const config = parseZodSchema< const config = parseZodSchema<
AgenticProjectConfig, AgenticProjectConfig,
ZodTypeDef, ZodTypeDef,
AgenticProjectConfigInput AgenticProjectConfigInput
>(agenticProjectConfigSchema, inputConfig) >(
strip
? agenticProjectConfigSchema.strip()
: agenticProjectConfigSchema.strict(),
inputConfig
)
const { name, pricingIntervals, pricingPlans, originUrl } = config const { name, pricingIntervals, pricingPlans, originUrl } = config
assert( assert(
@ -216,8 +224,8 @@ export async function validateAgenticProjectConfig(
} }
await validateOriginAdapter({ await validateOriginAdapter({
...opts,
label: `project "${name}"`, label: `project "${name}"`,
...opts,
originUrl, originUrl,
originAdapter: config.originAdapter originAdapter: config.originAdapter
}) })