feat: tiny kitty feet stomping around

pull/715/head
Travis Fischer 2025-05-26 02:37:29 +07:00
rodzic c7064c6f47
commit 817e726044
22 zmienionych plików z 152 dodań i 401 usunięć

Wyświetl plik

@ -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
})

Wyświetl plik

@ -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',

Wyświetl plik

@ -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>

Wyświetl plik

@ -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(),

Wyświetl plik

@ -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'

Wyświetl plik

@ -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": [
{

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -1,2 +1,3 @@
export * from './errors'
export type * from './types'
export * from './utils'

Wyświetl plik

@ -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>()
})

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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

Wyświetl plik

@ -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 {

Wyświetl plik

@ -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"
},

Wyświetl plik

@ -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"]
}

Wyświetl plik

@ -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>

Wyświetl plik

@ -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)
}

Wyświetl plik

@ -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'

Wyświetl plik

@ -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(),

Wyświetl plik

@ -1,3 +1,5 @@
import { z } from '@hono/zod-openapi'
export const authProviderTypeSchema = z
.union([
z.literal('github'),

Wyświetl plik

@ -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
}

Wyświetl plik

@ -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}`
)
}
}

Wyświetl plik

@ -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