kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: tiny kitty feet stomping around
rodzic
c7064c6f47
commit
817e726044
|
@ -1,4 +1,5 @@
|
|||
import { assert, parseZodSchema, pick, sha256 } from '@agentic/platform-core'
|
||||
import { validateOriginAdapter } from '@agentic/platform-schemas'
|
||||
import { validators } from '@agentic/platform-validators'
|
||||
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
|
||||
|
||||
|
@ -7,7 +8,6 @@ import { db, eq, schema } from '@/db'
|
|||
import { acl } from '@/lib/acl'
|
||||
import { normalizeDeploymentVersion } from '@/lib/deployments/normalize-deployment-version'
|
||||
import { publishDeployment } from '@/lib/deployments/publish-deployment'
|
||||
import { validateDeploymentOriginAdapter } from '@/lib/deployments/validate-deployment-origin-adapter'
|
||||
import { ensureAuthUser } from '@/lib/ensure-auth-user'
|
||||
import {
|
||||
openapiAuthenticatedSecuritySchemas,
|
||||
|
@ -99,10 +99,10 @@ export function registerV1DeploymentsCreateDeployment(
|
|||
})
|
||||
}
|
||||
|
||||
// Validate OpenAPI originUrl and originAdapter
|
||||
await validateDeploymentOriginAdapter({
|
||||
// Validate origin config, including any OpenAPI or MCP specs
|
||||
await validateOriginAdapter({
|
||||
...pick(body, 'originUrl', 'originAdapter'),
|
||||
deploymentIdentifier,
|
||||
label: `deployment "${deploymentIdentifier}"`,
|
||||
logger
|
||||
})
|
||||
|
||||
|
|
|
@ -137,8 +137,7 @@ export async function upsertStripePricing({
|
|||
|
||||
// Upsert the Stripe Price
|
||||
if (!project._stripePriceIdMap[pricingPlanLineItemHashForStripePrice]) {
|
||||
const interval =
|
||||
pricingPlanLineItem.interval ?? project.defaultPricingInterval
|
||||
const interval = pricingPlan.interval ?? project.defaultPricingInterval
|
||||
|
||||
const nickname = [
|
||||
'price',
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { assert } from '@agentic/platform-core'
|
||||
import { assert, type Logger } from '@agentic/platform-core'
|
||||
import * as Sentry from '@sentry/node'
|
||||
import { z } from 'zod'
|
||||
|
||||
|
@ -7,14 +7,6 @@ import { env } from '@/lib/env'
|
|||
|
||||
import { getTraceId } from './utils'
|
||||
|
||||
export interface Logger {
|
||||
trace(message?: any, ...detail: any[]): void
|
||||
debug(message?: any, ...detail: any[]): void
|
||||
info(message?: any, ...detail: any[]): void
|
||||
warn(message?: any, ...detail: any[]): void
|
||||
error(message?: any, ...detail: any[]): void
|
||||
}
|
||||
|
||||
const rawLogLevels = ['trace', 'debug', 'info', 'warn', 'error'] as const
|
||||
export const logLevelsSchema = z.enum(rawLogLevels)
|
||||
export type LogLevel = z.infer<typeof logLevelsSchema>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
import type { Logger } from '@agentic/platform-core'
|
||||
import { vi } from 'vitest'
|
||||
|
||||
import type { Logger } from '@/lib/logger'
|
||||
|
||||
export function setupMockLogger() {
|
||||
return {
|
||||
trace: vi.fn(),
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
import type { Logger } from '@agentic/platform-core'
|
||||
import type { Context } from 'hono'
|
||||
|
||||
import type { RawTeamMember, RawUser } from '@/db'
|
||||
|
||||
import type { Env } from './env'
|
||||
import type { Logger } from './logger'
|
||||
|
||||
export type Environment = Env['NODE_ENV']
|
||||
export type Service = 'api'
|
||||
|
|
|
@ -54,15 +54,15 @@ exports[`loadAgenticConfig > basic-raw-free-ts 1`] = `
|
|||
}
|
||||
`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-0 1`] = `[ZodValidationError: Validation error: Invalid project name "Test Invalid Name 0". Must be lower kebab-case with no spaces between 2 and 64 characters. at "name"]`;
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-0 1`] = `[Error: Invalid project name "Test Invalid Name 0". Must be lower kebab-case with no spaces between 2 and 64 characters. Example: "my-project" or "linkedin-resolver-23"]`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-1 1`] = `[ZodValidationError: Validation error: Invalid project name "Test-Invalid-Name-1". Must be lower kebab-case with no spaces between 2 and 64 characters. at "name"]`;
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-1 1`] = `[Error: Invalid project name "Test-Invalid-Name-1". Must be lower kebab-case with no spaces between 2 and 64 characters. Example: "my-project" or "linkedin-resolver-23"]`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-2 1`] = `[ZodValidationError: Validation error: Invalid project name "test_invalid_name_2". Must be lower kebab-case with no spaces between 2 and 64 characters. at "name"]`;
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-2 1`] = `[Error: Invalid project name "test_invalid_name_2". Must be lower kebab-case with no spaces between 2 and 64 characters. Example: "my-project" or "linkedin-resolver-23"]`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-3 1`] = `[ZodValidationError: Validation error: Required at "name"]`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-4 1`] = `[ZodValidationError: Validation error: Invalid project name "@foo/bar". Must be lower kebab-case with no spaces between 2 and 64 characters. at "name"]`;
|
||||
exports[`loadAgenticConfig > invalid: invalid-name-4 1`] = `[Error: Invalid project name "@foo/bar". Must be lower kebab-case with no spaces between 2 and 64 characters. Example: "my-project" or "linkedin-resolver-23"]`;
|
||||
|
||||
exports[`loadAgenticConfig > invalid: invalid-origin-url-0 1`] = `[Error: Invalid originUrl: must be a valid https URL]`;
|
||||
|
||||
|
@ -124,7 +124,6 @@ exports[`loadAgenticConfig > pricing-3-plans 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 999,
|
||||
"interval": "month",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
|
@ -142,7 +141,6 @@ exports[`loadAgenticConfig > pricing-3-plans 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 2999,
|
||||
"interval": "month",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
|
@ -183,13 +181,11 @@ exports[`loadAgenticConfig > pricing-custom-0 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 500,
|
||||
"interval": "month",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
{
|
||||
"billingScheme": "per_unit",
|
||||
"interval": "month",
|
||||
"rateLimit": {
|
||||
"interval": 2592000,
|
||||
"maxPerInterval": 1000,
|
||||
|
@ -207,13 +203,11 @@ exports[`loadAgenticConfig > pricing-custom-0 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 4800,
|
||||
"interval": "year",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
{
|
||||
"billingScheme": "per_unit",
|
||||
"interval": "year",
|
||||
"rateLimit": {
|
||||
"interval": 2592000,
|
||||
"maxPerInterval": 1500,
|
||||
|
@ -258,7 +252,6 @@ exports[`loadAgenticConfig > pricing-freemium 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 499,
|
||||
"interval": "month",
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
|
@ -300,7 +293,6 @@ exports[`loadAgenticConfig > pricing-monthly-annual 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 100,
|
||||
"interval": "month",
|
||||
"slug": "custom",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
|
@ -313,7 +305,6 @@ exports[`loadAgenticConfig > pricing-monthly-annual 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"amount": 70,
|
||||
"interval": "year",
|
||||
"slug": "custom",
|
||||
"usageType": "licensed",
|
||||
},
|
||||
|
@ -363,7 +354,6 @@ exports[`loadAgenticConfig > pricing-pay-as-you-go 1`] = `
|
|||
"lineItems": [
|
||||
{
|
||||
"billingScheme": "tiered",
|
||||
"interval": "month",
|
||||
"slug": "requests",
|
||||
"tiers": [
|
||||
{
|
||||
|
|
|
@ -1,13 +1,14 @@
|
|||
import type { AgenticProjectConfigOutput } from '@agentic/platform-schemas'
|
||||
import {
|
||||
type AgenticProjectConfig,
|
||||
validateAgenticProjectConfig
|
||||
} from '@agentic/platform-schemas'
|
||||
import { loadConfig } from 'unconfig'
|
||||
|
||||
import { validateAgenticConfig } from './validate-agentic-config'
|
||||
|
||||
export async function loadAgenticConfig({
|
||||
cwd
|
||||
}: {
|
||||
cwd?: string
|
||||
}): Promise<AgenticProjectConfigOutput> {
|
||||
}): Promise<AgenticProjectConfig> {
|
||||
const { config } = await loadConfig({
|
||||
cwd,
|
||||
sources: [
|
||||
|
@ -18,5 +19,5 @@ export async function loadAgenticConfig({
|
|||
]
|
||||
})
|
||||
|
||||
return validateAgenticConfig(config)
|
||||
return validateAgenticProjectConfig(config)
|
||||
}
|
||||
|
|
|
@ -1,2 +1,3 @@
|
|||
export * from './errors'
|
||||
export type * from './types'
|
||||
export * from './utils'
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
import { expectTypeOf, test } from 'vitest'
|
||||
|
||||
import type { Logger } from './types'
|
||||
|
||||
test('Logger type is compatible with Console', () => {
|
||||
expectTypeOf<Console>().toExtend<Logger>()
|
||||
})
|
|
@ -0,0 +1,7 @@
|
|||
export interface Logger {
|
||||
trace(message?: any, ...detail: any[]): void
|
||||
debug(message?: any, ...detail: any[]): void
|
||||
info(message?: any, ...detail: any[]): void
|
||||
warn(message?: any, ...detail: any[]): void
|
||||
error(message?: any, ...detail: any[]): void
|
||||
}
|
|
@ -1,5 +1,3 @@
|
|||
export type Logger = Pick<Console, 'debug' | 'info' | 'warn' | 'error'>
|
||||
|
||||
// These loose OpenAPI types are taken from https://github.com/openapi-ts/openapi-typescript/blob/main/packages/openapi-typescript/src/types.ts
|
||||
|
||||
// Note: these OpenAPI types are meant only for internal use, not external
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
import { fileURLToPath } from 'node:url'
|
||||
|
||||
import { assert, parseJson } from '@agentic/platform-core'
|
||||
import { assert, type Logger, parseJson } from '@agentic/platform-core'
|
||||
import {
|
||||
BaseResolver,
|
||||
bundle,
|
||||
|
@ -12,7 +12,7 @@ import {
|
|||
Source
|
||||
} from '@redocly/openapi-core'
|
||||
|
||||
import type { Logger, LooseOpenAPI3Spec } from './types'
|
||||
import type { LooseOpenAPI3Spec } from './types'
|
||||
import { getDefaultRedoclyConfig } from './redocly-config'
|
||||
|
||||
interface ParseSchemaOptions {
|
||||
|
|
|
@ -23,11 +23,14 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@agentic/platform-core": "workspace:*",
|
||||
"@agentic/platform-openapi": "workspace:*",
|
||||
"@agentic/platform-validators": "workspace:*",
|
||||
"@hono/zod-openapi": "^0.19.6",
|
||||
"ms": "^2.1.3",
|
||||
"zod": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ms": "^2.1.0",
|
||||
"restore-cursor": "catalog:",
|
||||
"zod-to-json-schema": "^3.24.5"
|
||||
},
|
||||
|
|
|
@ -1,280 +0,0 @@
|
|||
{
|
||||
"$schema": "https://json-schema.org/draft-07/schema",
|
||||
"title": "Agentic Project Config Schema",
|
||||
"description": "JSON Schema used by `agentic.config.{ts,js,json}` files to configure Agentic projects.",
|
||||
"additionalProperties": false,
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "The name of the project."
|
||||
},
|
||||
"description": {
|
||||
"type": "string",
|
||||
"description": "A one-sentence description of the project."
|
||||
},
|
||||
"readme": {
|
||||
"type": "string",
|
||||
"description": "A readme documenting the project (supports GitHub-flavored markdown)."
|
||||
},
|
||||
"sourceUrl": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "Optional URL to the source code for the project."
|
||||
},
|
||||
"iconUrl": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "Optional logo image URL to use for the project. Logos should have a square aspect ratio."
|
||||
},
|
||||
"originUrl": {
|
||||
"type": "string",
|
||||
"format": "uri",
|
||||
"description": "Required base URL of the externally hosted origin API server. Must be a valid `https` URL.\n\nNOTE: 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."
|
||||
},
|
||||
"originAdapter": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "openapi"
|
||||
},
|
||||
"spec": {
|
||||
"type": "string",
|
||||
"description": "JSON stringified OpenAPI spec describing the origin API server."
|
||||
},
|
||||
"location": {
|
||||
"type": "string",
|
||||
"const": "external"
|
||||
}
|
||||
},
|
||||
"required": ["type", "spec", "location"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"type": {
|
||||
"type": "string",
|
||||
"const": "raw"
|
||||
},
|
||||
"location": {
|
||||
"$ref": "#/properties/originAdapter/anyOf/0/properties/location"
|
||||
}
|
||||
},
|
||||
"required": ["type", "location"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"description": "Deployment origin API adapter is used to configure the origin API server downstream from Agentic's API gateway. It specifies whether the origin API server denoted by `originUrl` is hosted externally or deployed internally to Agentic's infrastructure. It also specifies the format for how origin tools / services are defined: either as an OpenAPI spec, an MCP server, or as a raw HTTP REST API.\n\nNOTE: 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.",
|
||||
"default": {
|
||||
"location": "external",
|
||||
"type": "raw"
|
||||
}
|
||||
},
|
||||
"pricingPlans": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1
|
||||
},
|
||||
"slug": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"description": "PricingPlan slug (\"free\", \"starter-monthly\", \"pro-annual\", etc)"
|
||||
},
|
||||
"interval": {
|
||||
"type": "string",
|
||||
"enum": ["day", "week", "month", "year"],
|
||||
"description": "The frequency at which a subscription is billed."
|
||||
},
|
||||
"desc": {
|
||||
"type": "string"
|
||||
},
|
||||
"features": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"trialPeriodDays": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"lineItems": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "string"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "base"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "requests"
|
||||
}
|
||||
]
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/properties/pricingPlans/items/properties/interval",
|
||||
"description": "The frequency at which a subscription is billed."
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"usageType": {
|
||||
"type": "string",
|
||||
"const": "licensed"
|
||||
},
|
||||
"amount": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
}
|
||||
},
|
||||
"required": ["slug", "usageType", "amount"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"slug": {
|
||||
"$ref": "#/properties/pricingPlans/items/properties/lineItems/items/anyOf/0/properties/slug"
|
||||
},
|
||||
"interval": {
|
||||
"$ref": "#/properties/pricingPlans/items/properties/lineItems/items/anyOf/0/properties/interval"
|
||||
},
|
||||
"label": {
|
||||
"$ref": "#/properties/pricingPlans/items/properties/lineItems/items/anyOf/0/properties/label"
|
||||
},
|
||||
"usageType": {
|
||||
"type": "string",
|
||||
"const": "metered"
|
||||
},
|
||||
"unitLabel": {
|
||||
"type": "string"
|
||||
},
|
||||
"rateLimit": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"interval": {
|
||||
"type": "number"
|
||||
},
|
||||
"maxPerInterval": {
|
||||
"type": "number"
|
||||
}
|
||||
},
|
||||
"required": ["interval", "maxPerInterval"],
|
||||
"additionalProperties": false
|
||||
},
|
||||
"billingScheme": {
|
||||
"type": "string",
|
||||
"enum": ["per_unit", "tiered"]
|
||||
},
|
||||
"unitAmount": {
|
||||
"type": "number",
|
||||
"minimum": 0
|
||||
},
|
||||
"tiersMode": {
|
||||
"type": "string",
|
||||
"enum": ["graduated", "volume"]
|
||||
},
|
||||
"tiers": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"unitAmount": {
|
||||
"type": "number"
|
||||
},
|
||||
"flatAmount": {
|
||||
"type": "number"
|
||||
},
|
||||
"upTo": {
|
||||
"anyOf": [
|
||||
{
|
||||
"type": "number"
|
||||
},
|
||||
{
|
||||
"type": "string",
|
||||
"const": "inf"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["upTo"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"defaultAggregation": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"formula": {
|
||||
"type": "string",
|
||||
"enum": ["sum", "count", "last"],
|
||||
"default": "sum"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"transformQuantity": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"divideBy": {
|
||||
"type": "number",
|
||||
"exclusiveMinimum": 0
|
||||
},
|
||||
"round": {
|
||||
"type": "string",
|
||||
"enum": ["down", "up"]
|
||||
}
|
||||
},
|
||||
"required": ["divideBy", "round"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["slug", "usageType", "billingScheme"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
],
|
||||
"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."
|
||||
},
|
||||
"minItems": 1,
|
||||
"maxItems": 20
|
||||
}
|
||||
},
|
||||
"required": ["name", "slug", "lineItems"],
|
||||
"additionalProperties": false,
|
||||
"description": "Represents the config for a Stripe subscription with one or more PricingPlanLineItems."
|
||||
},
|
||||
"minItems": 1,
|
||||
"description": "List of PricingPlans to enable subscriptions for the project. Defaults to a single free tier.",
|
||||
"default": [
|
||||
{
|
||||
"name": "Free",
|
||||
"slug": "free",
|
||||
"lineItems": [
|
||||
{
|
||||
"slug": "base",
|
||||
"usageType": "licensed",
|
||||
"amount": 0
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"required": ["name", "originUrl"]
|
||||
}
|
|
@ -1,4 +1,3 @@
|
|||
import { validators } from '@agentic/platform-validators'
|
||||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
import {
|
||||
|
@ -39,15 +38,7 @@ export const agenticProjectConfigSchema = z.object({
|
|||
* @example "my-project"
|
||||
* @example "linkedin-resolver-23"
|
||||
*/
|
||||
name: z
|
||||
.string()
|
||||
.describe('Name of the project.')
|
||||
.refine(
|
||||
(name) => validators.projectName(name),
|
||||
(name) => ({
|
||||
message: `Invalid project name "${name}". Must be lower kebab-case with no spaces between 2 and 64 characters.`
|
||||
})
|
||||
),
|
||||
name: z.string().nonempty().describe('Name of the project.'),
|
||||
|
||||
/** Optional short description of the project. */
|
||||
description: z
|
||||
|
@ -111,9 +102,9 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
|||
* 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.
|
||||
* specify their `interval` property. Otherwise, all PricingPlans must
|
||||
* specify their `interval` property to differentiate between different
|
||||
* pricing intervals.
|
||||
*/
|
||||
pricingIntervals: z
|
||||
.array(pricingIntervalSchema)
|
||||
|
@ -127,6 +118,4 @@ NOTE: Agentic currently only supports \`external\` API servers. If you'd like to
|
|||
export type AgenticProjectConfigInput = z.input<
|
||||
typeof agenticProjectConfigSchema
|
||||
>
|
||||
export type AgenticProjectConfigOutput = z.output<
|
||||
typeof agenticProjectConfigSchema
|
||||
>
|
||||
export type AgenticProjectConfig = z.output<typeof agenticProjectConfigSchema>
|
||||
|
|
|
@ -1,13 +1,13 @@
|
|||
import { parseZodSchema } from '@agentic/platform-core'
|
||||
|
||||
import {
|
||||
type AgenticProjectConfig,
|
||||
type AgenticProjectConfigInput,
|
||||
type AgenticProjectConfigOutput,
|
||||
agenticProjectConfigSchema
|
||||
} from './agentic-project-config-schema'
|
||||
|
||||
export function defineConfig(
|
||||
config: AgenticProjectConfigInput
|
||||
): AgenticProjectConfigOutput {
|
||||
): AgenticProjectConfig {
|
||||
return parseZodSchema(agenticProjectConfigSchema, config)
|
||||
}
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
export * from './agentic-project-config-schema'
|
||||
export * from './define-config'
|
||||
export * from './schemas'
|
||||
export * from './validate-agentic-project-config'
|
||||
export * from './validate-origin-adapter'
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { z } from '@hono/zod-openapi'
|
||||
import parseIntervalAsMs from 'ms'
|
||||
|
||||
export const webhookSchema = z
|
||||
.object({
|
||||
|
@ -13,10 +14,55 @@ export type Webhook = z.infer<typeof webhookSchema>
|
|||
*/
|
||||
export const rateLimitSchema = z
|
||||
.object({
|
||||
interval: z.number(), // seconds
|
||||
maxPerInterval: z.number() // unitless
|
||||
/**
|
||||
* 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: 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
|
||||
|
||||
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}"`,
|
||||
path: ctx.path
|
||||
})
|
||||
|
||||
return z.NEVER
|
||||
}
|
||||
})
|
||||
]),
|
||||
|
||||
/**
|
||||
* Maximum number of operations per interval (unitless).
|
||||
*/
|
||||
maxPerInterval: z
|
||||
.number()
|
||||
.describe('Maximum number of operations per interval (unitless).')
|
||||
})
|
||||
.openapi('RateLimit')
|
||||
export type RateLimitInput = z.input<typeof rateLimitSchema>
|
||||
export type RateLimit = z.infer<typeof rateLimitSchema>
|
||||
|
||||
/**
|
||||
|
@ -109,21 +155,13 @@ const commonPricingPlanLineItemSchema = z.object({
|
|||
*
|
||||
* The `base` slug is reserved for a plan's default `licensed` line-item.
|
||||
*
|
||||
* The `requests` slug is reserved for charging using `metered billing based
|
||||
* 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 LineItems.
|
||||
*/
|
||||
slug: z.union([z.string(), z.literal('base'), z.literal('requests')]),
|
||||
|
||||
/**
|
||||
* The frequency at which a subscription is billed.
|
||||
*
|
||||
* Only optional on free plans (when `PricingPlan.slug` is `free`), since
|
||||
* free plans don't depend on a billing interval.
|
||||
*/
|
||||
interval: pricingIntervalSchema.optional(),
|
||||
|
||||
/**
|
||||
* Optional label for the line-item which will be displayed on customer bills.
|
||||
*
|
||||
|
@ -317,7 +355,7 @@ export const pricingPlanSchema = z
|
|||
.openapi('slug', { example: 'starter-monthly' }),
|
||||
|
||||
/**
|
||||
* The frequency at which a subscription is billed.
|
||||
* The frequency at which this subscription is billed.
|
||||
*/
|
||||
interval: pricingIntervalSchema.optional(),
|
||||
|
||||
|
|
|
@ -1,3 +1,5 @@
|
|||
import { z } from '@hono/zod-openapi'
|
||||
|
||||
export const authProviderTypeSchema = z
|
||||
.union([
|
||||
z.literal('github'),
|
||||
|
|
|
@ -1,22 +1,30 @@
|
|||
import type { ZodTypeDef } from 'zod'
|
||||
import { assert, parseZodSchema } from '@agentic/platform-core'
|
||||
import {
|
||||
type AgenticProjectConfigInput,
|
||||
type AgenticProjectConfigOutput,
|
||||
agenticProjectConfigSchema,
|
||||
type PricingPlanLineItem
|
||||
} from '@agentic/platform-schemas'
|
||||
import { assert, type Logger, parseZodSchema } from '@agentic/platform-core'
|
||||
import { validators } from '@agentic/platform-validators'
|
||||
|
||||
export async function validateAgenticConfig(
|
||||
inputConfig: unknown
|
||||
): Promise<AgenticProjectConfigOutput> {
|
||||
import type { PricingPlanLineItem } from './schemas'
|
||||
import {
|
||||
type AgenticProjectConfig,
|
||||
type AgenticProjectConfigInput,
|
||||
agenticProjectConfigSchema
|
||||
} from './agentic-project-config-schema'
|
||||
import { validateOriginAdapter } from './validate-origin-adapter'
|
||||
|
||||
export async function validateAgenticProjectConfig(
|
||||
inputConfig: unknown,
|
||||
opts: { logger?: Logger; cwd?: URL } = {}
|
||||
): Promise<AgenticProjectConfig> {
|
||||
const config = parseZodSchema<
|
||||
AgenticProjectConfigOutput,
|
||||
AgenticProjectConfig,
|
||||
ZodTypeDef,
|
||||
AgenticProjectConfigInput
|
||||
>(agenticProjectConfigSchema, inputConfig)
|
||||
|
||||
const { pricingIntervals, pricingPlans, originUrl } = config
|
||||
const { name, pricingIntervals, pricingPlans, originUrl } = config
|
||||
assert(
|
||||
validators.projectName(name),
|
||||
`Invalid project name "${name}". Must be lower kebab-case with no spaces between 2 and 64 characters. Example: "my-project" or "linkedin-resolver-23"`
|
||||
)
|
||||
assert(
|
||||
pricingPlans?.length,
|
||||
'Invalid pricingPlans: must be a non-empty array'
|
||||
|
@ -67,20 +75,6 @@ export async function validateAgenticConfig(
|
|||
pricingPlan.interval !== undefined,
|
||||
`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,
|
||||
`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),
|
||||
`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
|
||||
|
@ -102,15 +96,6 @@ export async function validateAgenticConfig(
|
|||
if (pricingPlan.slug === 'free') continue
|
||||
|
||||
pricingPlan.interval ??= defaultPricingInterval
|
||||
|
||||
for (const lineItem of pricingPlan.lineItems) {
|
||||
lineItem.interval ??= defaultPricingInterval
|
||||
|
||||
assert(
|
||||
pricingIntervalsSet.has(lineItem.interval),
|
||||
`Invalid pricingPlan "${pricingPlan.slug}": PricingPlan "${pricingPlan.slug}" LineItem "${lineItem.slug}" has invalid interval "${pricingPlan.interval}" which is not included in the "pricingIntervals" array.`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -214,5 +199,12 @@ export async function validateAgenticConfig(
|
|||
}
|
||||
}
|
||||
|
||||
await validateOriginAdapter({
|
||||
...opts,
|
||||
label: `project "${name}"`,
|
||||
originUrl,
|
||||
originAdapter: config.originAdapter
|
||||
})
|
||||
|
||||
return config
|
||||
}
|
|
@ -1,40 +1,37 @@
|
|||
import type { DeploymentOriginAdapter } from '@agentic/platform-schemas'
|
||||
import { assert } from '@agentic/platform-core'
|
||||
import { assert, type Logger } from '@agentic/platform-core'
|
||||
import { validateOpenAPISpec } from '@agentic/platform-openapi'
|
||||
|
||||
import type { Logger } from '@/lib/logger'
|
||||
|
||||
/**
|
||||
* Validates and normalizes the origin adapter config for a deployment.
|
||||
* Validates and normalizes the origin adapter config for a project.
|
||||
*
|
||||
* NOTE: This method may mutate `originAdapter.spec`.
|
||||
*/
|
||||
export async function validateDeploymentOriginAdapter({
|
||||
deploymentIdentifier,
|
||||
export async function validateOriginAdapter({
|
||||
originUrl,
|
||||
originAdapter,
|
||||
label,
|
||||
cwd,
|
||||
logger
|
||||
}: {
|
||||
deploymentIdentifier: string
|
||||
originUrl: string
|
||||
originAdapter: DeploymentOriginAdapter
|
||||
logger: Logger
|
||||
label: string
|
||||
cwd?: URL
|
||||
logger?: Logger
|
||||
}): Promise<void> {
|
||||
assert(
|
||||
originUrl,
|
||||
400,
|
||||
`Origin URL is required for deployment "${deploymentIdentifier}"`
|
||||
)
|
||||
assert(originUrl, 400, `Origin URL is required for ${label}`)
|
||||
|
||||
if (originAdapter.type === 'openapi') {
|
||||
assert(
|
||||
originAdapter.spec,
|
||||
400,
|
||||
`OpenAPI spec is required for deployment "${deploymentIdentifier}" with origin adapter type set to "openapi"`
|
||||
`OpenAPI spec is required for ${label} with origin adapter type set to "openapi"`
|
||||
)
|
||||
|
||||
// Validate and normalize the OpenAPI spec
|
||||
const openapiSpec = await validateOpenAPISpec(originAdapter.spec, {
|
||||
cwd,
|
||||
logger
|
||||
})
|
||||
|
||||
|
@ -54,7 +51,7 @@ export async function validateDeploymentOriginAdapter({
|
|||
assert(
|
||||
originAdapter.type === 'raw',
|
||||
400,
|
||||
`Invalid origin adapter type "${originAdapter.type}" for deployment "${deploymentIdentifier}"`
|
||||
`Invalid origin adapter type "${originAdapter.type}" for ${label}`
|
||||
)
|
||||
}
|
||||
}
|
|
@ -325,16 +325,25 @@ importers:
|
|||
'@agentic/platform-core':
|
||||
specifier: workspace:*
|
||||
version: link:../core
|
||||
'@agentic/platform-openapi':
|
||||
specifier: workspace:*
|
||||
version: link:../openapi
|
||||
'@agentic/platform-validators':
|
||||
specifier: workspace:*
|
||||
version: link:../validators
|
||||
'@hono/zod-openapi':
|
||||
specifier: ^0.19.6
|
||||
version: 0.19.6(hono@4.7.9)(zod@3.24.4)
|
||||
ms:
|
||||
specifier: ^2.1.3
|
||||
version: 2.1.3
|
||||
zod:
|
||||
specifier: 'catalog:'
|
||||
version: 3.24.4
|
||||
devDependencies:
|
||||
'@types/ms':
|
||||
specifier: ^2.1.0
|
||||
version: 2.1.0
|
||||
restore-cursor:
|
||||
specifier: 'catalog:'
|
||||
version: 5.1.0
|
||||
|
@ -1415,6 +1424,9 @@ packages:
|
|||
'@types/json5@0.0.29':
|
||||
resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==}
|
||||
|
||||
'@types/ms@2.1.0':
|
||||
resolution: {integrity: sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==}
|
||||
|
||||
'@types/mysql@2.15.26':
|
||||
resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==}
|
||||
|
||||
|
@ -5080,6 +5092,8 @@ snapshots:
|
|||
|
||||
'@types/json5@0.0.29': {}
|
||||
|
||||
'@types/ms@2.1.0': {}
|
||||
|
||||
'@types/mysql@2.15.26':
|
||||
dependencies:
|
||||
'@types/node': 22.15.18
|
||||
|
|
Ładowanie…
Reference in New Issue