feat: add virtual urls to projects/deployments/consumer models to make it easier to link and debug them

pull/715/head
Travis Fischer 2025-06-28 00:44:11 -05:00
rodzic 66783f1038
commit 3f34cdccae
40 zmienionych plików z 374 dodań i 111 usunięć

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerAdminSelectSchema } from '@/db/schema'
import { aclAdmin } from '@/lib/acl-admin'
import {
openapiAuthenticatedSecuritySchemas,
@ -57,6 +58,6 @@ export function registerV1AdminActivateConsumer(
assert(consumer, 404, `Consumer not found "${consumerId}"`)
setAdminCacheControlForConsumer(c, consumer)
return c.json(parseZodSchema(schema.consumerAdminSelectSchema, consumer))
return c.json(parseConsumerAdminSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerAdminSelectSchema } from '@/db/schema'
import { aclAdmin } from '@/lib/acl-admin'
import {
openapiAuthenticatedSecuritySchemas,
@ -55,6 +56,6 @@ export function registerV1AdminGetConsumerByToken(
assert(consumer, 404, `API token not found "${token}"`)
setAdminCacheControlForConsumer(c, consumer)
return c.json(parseZodSchema(schema.consumerAdminSelectSchema, consumer))
return c.json(parseConsumerAdminSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { parseZodSchema, pick } from '@agentic/platform-core'
import { pick } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { upsertConsumerStripeCheckout } from '@/lib/consumers/upsert-consumer-stripe-checkout'
import {
openapiAuthenticatedSecuritySchemas,
@ -64,7 +65,7 @@ export function registerV1CreateConsumerCheckoutSession(
return c.json({
checkoutSession: pick(checkoutSession, 'id', 'url'),
consumer: parseZodSchema(schema.consumerSelectSchema, consumer)
consumer: parseConsumerSelectSchema(consumer)
})
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { upsertConsumer } from '@/lib/consumers/upsert-consumer'
import {
openapiAuthenticatedSecuritySchemas,
@ -53,6 +53,6 @@ export function registerV1CreateConsumer(
const body = c.req.valid('json')
const consumer = await upsertConsumer(c, body)
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
return c.json(parseConsumerSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { aclPublicProject } from '@/lib/acl-public-project'
import {
@ -49,7 +50,7 @@ export function registerV1GetConsumerByProjectIdentifier(
where: eq(schema.projects.identifier, projectIdentifier)
})
assert(project, 404, `Project not found "${projectIdentifier}"`)
await aclPublicProject(project)
aclPublicProject(project)
const consumer = await db.query.consumers.findFirst({
where: and(
@ -67,6 +68,6 @@ export function registerV1GetConsumerByProjectIdentifier(
)
await acl(c, consumer, { label: 'Consumer' })
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
return c.json(parseConsumerSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import {
openapiAuthenticatedSecuritySchemas,
@ -51,6 +52,6 @@ export function registerV1GetConsumer(app: OpenAPIHono<AuthenticatedHonoEnv>) {
assert(consumer, 404, `Consumer not found "${consumerId}"`)
await acl(c, consumer, { label: 'Consumer' })
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
return c.json(parseConsumerSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerSelectArraySchema } from '@/db/schema'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import {
openapiAuthenticatedSecuritySchemas,
@ -62,8 +62,6 @@ export function registerV1ListConsumers(
limit
})
return c.json(
parseZodSchema(z.array(schema.consumerSelectSchema), consumers)
)
return c.json(parseConsumerSelectArraySchema(consumers))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerSelectArraySchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import {
openapiAuthenticatedSecuritySchemas,
@ -71,8 +72,6 @@ export function registerV1ListConsumersForProject(
limit
})
return c.json(
parseZodSchema(z.array(schema.consumerSelectSchema), consumers)
)
return c.json(parseConsumerSelectArraySchema(consumers))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { createConsumerToken } from '@/lib/create-consumer-token'
import {
@ -59,6 +60,6 @@ export function registerV1RefreshConsumerToken(
.returning()
assert(consumer, 500, 'Error updating consumer')
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
return c.json(parseConsumerSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseConsumerSelectSchema } from '@/db/schema'
import { upsertConsumer } from '@/lib/consumers/upsert-consumer'
import {
openapiAuthenticatedSecuritySchemas,
@ -61,6 +61,6 @@ export function registerV1UpdateConsumer(
consumerId
})
return c.json(parseZodSchema(schema.consumerSelectSchema, consumer))
return c.json(parseConsumerSelectSchema(consumer))
})
}

Wyświetl plik

@ -1,10 +1,10 @@
import type { Consumer } from '@/db'
import type { RawConsumer } from '@/db'
import type { AuthenticatedHonoContext } from '@/lib/types'
import { setPublicCacheControl } from '@/lib/cache-control'
export function setAdminCacheControlForConsumer(
c: AuthenticatedHonoContext,
consumer: Consumer
consumer: RawConsumer
) {
if (
consumer.plan === 'free' ||

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseDeploymentAdminSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { aclAdmin } from '@/lib/acl-admin'
import { tryGetDeploymentByIdentifier } from '@/lib/deployments/try-get-deployment-by-identifier'
@ -67,7 +68,7 @@ export function registerV1AdminGetDeploymentByIdentifier(
const hasPopulateProject = populate.includes('project')
return c.json(
parseZodSchema(schema.deploymentAdminSelectSchema, {
parseDeploymentAdminSelectSchema({
...deployment,
...(hasPopulateProject ? { project } : {}),
_secret: project._secret

Wyświetl plik

@ -1,5 +1,5 @@
import { resolveAgenticProjectConfig } from '@agentic/platform'
import { assert, parseZodSchema, sha256, slugify } from '@agentic/platform-core'
import { assert, sha256, slugify } from '@agentic/platform-core'
import {
isValidDeploymentIdentifier,
parseProjectIdentifier
@ -8,6 +8,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { normalizeDeploymentVersion } from '@/lib/deployments/normalize-deployment-version'
import { publishDeployment } from '@/lib/deployments/publish-deployment'
@ -189,6 +190,6 @@ export function registerV1CreateDeployment(
})
}
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
return c.json(parseDeploymentSelectSchema(deployment))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { tryGetDeploymentByIdentifier } from '@/lib/deployments/try-get-deployment-by-identifier'
import {
@ -53,6 +54,6 @@ export function registerV1GetDeploymentByIdentifier(
assert(deployment, 404, `Deployment not found "${deploymentIdentifier}"`)
await acl(c, deployment, { label: 'Deployment' })
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
return c.json(parseDeploymentSelectSchema(deployment))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import {
@ -54,6 +55,6 @@ export function registerV1GetDeployment(
assert(deployment, 404, `Deployment not found "${deploymentId}"`)
await acl(c, deployment, { label: 'Deployment' })
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
return c.json(parseDeploymentSelectSchema(deployment))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import { schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { aclPublicProject } from '@/lib/acl-public-project'
import { tryGetDeploymentByIdentifier } from '@/lib/deployments/try-get-deployment-by-identifier'
import {
@ -57,8 +58,8 @@ export function registerV1GetPublicDeploymentByIdentifier(
404,
`Project not found for deployment "${deploymentIdentifier}"`
)
await aclPublicProject(deployment.project!)
aclPublicProject(deployment.project!)
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
return c.json(parseDeploymentSelectSchema(deployment))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { and, db, eq, schema } from '@/db'
import { parseDeploymentSelectArraySchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import {
@ -88,8 +88,6 @@ export function registerV1ListDeployments(
limit
})
return c.json(
parseZodSchema(z.array(schema.deploymentSelectSchema), deployments)
)
return c.json(parseDeploymentSelectArraySchema(deployments))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import { publishDeployment } from '@/lib/deployments/publish-deployment'
@ -63,8 +64,6 @@ export function registerV1PublishDeployment(
version
})
return c.json(
parseZodSchema(schema.deploymentSelectSchema, publishedDeployment)
)
return c.json(parseDeploymentSelectSchema(publishedDeployment))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseDeploymentSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import { getDeploymentById } from '@/lib/deployments/get-deployment-by-id'
import {
@ -65,6 +66,6 @@ export function registerV1UpdateDeployment(
.returning()
assert(deployment, 500, `Failed to update deployment "${deploymentId}"`)
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
return c.json(parseDeploymentSelectSchema(deployment))
})
}

Wyświetl plik

@ -1,9 +1,10 @@
import { assert, parseZodSchema, sha256 } from '@agentic/platform-core'
import { assert, sha256 } from '@agentic/platform-core'
import { parseProjectIdentifier } from '@agentic/platform-validators'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import { env } from '@/lib/env'
import {
@ -86,6 +87,6 @@ export function registerV1CreateProject(
.returning()
assert(project, 500, `Failed to create project "${body.name}"`)
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import {
openapiAuthenticatedSecuritySchemas,
@ -53,6 +54,6 @@ export function registerV1GetProjectByIdentifier(
assert(project, 404, `Project not found "${projectIdentifier}"`)
await acl(c, project, { label: 'Project' })
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import {
openapiAuthenticatedSecuritySchemas,
@ -52,6 +53,6 @@ export function registerV1GetProject(app: OpenAPIHono<AuthenticatedHonoEnv>) {
assert(project, 404, `Project not found "${projectId}"`)
await acl(c, project, { label: 'Project' })
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import { db, eq, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { aclPublicProject } from '@/lib/acl-public-project'
import {
openapiAuthenticatedSecuritySchemas,
@ -50,8 +50,8 @@ export function registerV1GetPublicProjectByIdentifier(
...Object.fromEntries(populate.map((field) => [field, true]))
}
})
await aclPublicProject(project, projectIdentifier)
aclPublicProject(project, projectIdentifier)
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import { db, eq, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { aclPublicProject } from '@/lib/acl-public-project'
import {
openapiAuthenticatedSecuritySchemas,
@ -49,8 +49,8 @@ export function registerV1GetPublicProject(app: OpenAPIHono<DefaultHonoEnv>) {
...Object.fromEntries(populate.map((field) => [field, true]))
}
})
await aclPublicProject(project, projectId)
aclPublicProject(project, projectId)
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseProjectSelectArraySchema } from '@/db/schema'
import { ensureAuthUser } from '@/lib/ensure-auth-user'
import {
openapiAuthenticatedSecuritySchemas,
@ -65,6 +65,6 @@ export function registerV1ListProjects(app: OpenAPIHono<AuthenticatedHonoEnv>) {
limit
})
return c.json(parseZodSchema(z.array(schema.projectSelectSchema), projects))
return c.json(parseProjectSelectArraySchema(projects))
})
}

Wyświetl plik

@ -1,8 +1,8 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono, z } from '@hono/zod-openapi'
import { and, db, eq, isNotNull, schema } from '@/db'
import { parseProjectSelectArraySchema } from '@/db/schema'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponses
@ -61,6 +61,6 @@ export function registerV1ListPublicProjects(app: OpenAPIHono<DefaultHonoEnv>) {
limit
})
return c.json(parseZodSchema(z.array(schema.projectSelectSchema), projects))
return c.json(parseProjectSelectArraySchema(projects))
})
}

Wyświetl plik

@ -1,8 +1,9 @@
import { assert, parseZodSchema } from '@agentic/platform-core'
import { assert } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedHonoEnv } from '@/lib/types'
import { db, eq, schema } from '@/db'
import { parseProjectSelectSchema } from '@/db/schema'
import { acl } from '@/lib/acl'
import {
openapiAuthenticatedSecuritySchemas,
@ -66,6 +67,6 @@ export function registerV1UpdateProject(
.returning()
assert(project, 500, `Failed to update project "${projectId}"`)
return c.json(parseZodSchema(schema.projectSelectSchema, project))
return c.json(parseProjectSelectSchema(project))
})
}

Wyświetl plik

@ -1,3 +1,4 @@
import { parseZodSchema } from '@agentic/platform-core'
import {
type StripeSubscriptionItemIdMap,
stripeSubscriptionItemIdMapSchema
@ -12,6 +13,9 @@ import {
} from '@fisch0920/drizzle-orm/pg-core'
import { z } from '@hono/zod-openapi'
import { env } from '@/lib/env'
import type { Consumer } from '../types'
import {
consumerIdSchema,
deploymentIdSchema,
@ -185,6 +189,21 @@ export const consumerSelectSchema = createSelectSchema(consumers, {
.optional()
})
.strip()
// These are all derived virtual URLs that are not stored in the database
.extend({
/**
* A private admin URL for managing the customer's subscription. This URL
* is only accessible by the customer.
*
* @example https://agentic.so/app/consumers/cons_123
*/
adminUrl: z
.string()
.url()
.describe(
"A private admin URL for managing the customer's subscription. This URL is only accessible by the customer."
)
})
.describe(
`A Consumer represents a user who has subscribed to a Project and is used
to track usage and billing.
@ -196,12 +215,36 @@ owner's connected Stripe account if the project has Stripe Connect enabled.`
)
.openapi('Consumer')
export function parseConsumerSelectSchema(
consumer: Record<string, any>
): Consumer {
return parseZodSchema(consumerSelectSchema, {
...consumer,
adminUrl: `${env.AGENTIC_WEB_BASE_URL}/app/consumers/${consumer.id}`
})
}
export function parseConsumerSelectArraySchema(
consumers: Record<string, any>[]
): Consumer[] {
return consumers.map(parseConsumerSelectSchema)
}
export const consumerAdminSelectSchema = consumerSelectSchema
.extend({
_stripeCustomerId: z.string().nonempty()
})
.openapi('AdminConsumer')
export function parseConsumerAdminSelectSchema(
consumer: Record<string, any>
): Consumer {
return parseZodSchema(consumerAdminSelectSchema, {
...parseConsumerSelectSchema(consumer),
...consumer
})
}
export const consumerInsertSchema = createInsertSchema(consumers, {
deploymentId: deploymentIdSchema.optional(),

Wyświetl plik

@ -1,3 +1,4 @@
import { parseZodSchema } from '@agentic/platform-core'
import {
agenticProjectConfigSchema,
defaultRequestsRateLimit,
@ -8,7 +9,10 @@ import {
type Tool,
type ToolConfig
} from '@agentic/platform-types'
import { isValidDeploymentHash } from '@agentic/platform-validators'
import {
isValidDeploymentHash,
parseDeploymentIdentifier
} from '@agentic/platform-validators'
import { relations } from '@fisch0920/drizzle-orm'
import {
boolean,
@ -20,6 +24,9 @@ import {
} from '@fisch0920/drizzle-orm/pg-core'
import { z } from '@hono/zod-openapi'
import { env } from '@/lib/env'
import type { Deployment } from '../types'
import {
deploymentIdentifierSchema,
deploymentIdSchema,
@ -209,6 +216,59 @@ export const deploymentSelectSchema = createSelectSchema(deployments, {
// project: z.object({}).optional().openapi('Project', { type: 'object' })
})
.strip()
// These are all derived virtual URLs that are not stored in the database
.extend({
/**
* The public base HTTP URL for the deployment supporting HTTP POST requests
* for individual tools at `/tool-name` subpaths.
*
* @example https://gateway.agentic.com/@agentic/search@latest
*/
gatewayBaseUrl: z
.string()
.url()
.describe(
'The public base HTTP URL for the deployment supporting HTTP POST requests for individual tools at `/tool-name` subpaths.'
),
/**
* The public MCP URL for the deployment supporting the Streamable HTTP
* transport.
*
* @example https://gateway.agentic.com/@agentic/search@latest/mcp
*/
gatewayMcpUrl: z
.string()
.url()
.describe(
'The public MCP URL for the deployment supporting the Streamable HTTP transport.'
),
/**
* The public marketplace URL for the deployment's project.
*
* Note that only published deployments are visible on the marketplace.
*
* @example https://agentic.so/marketplace/projects/@agentic/search
*/
marketplaceUrl: z
.string()
.url()
.describe("The public marketplace URL for the deployment's project."),
/**
* A private admin URL for managing the deployment. This URL is only accessible
* by project owners.
*
* @example https://agentic.so/app/projects/@agentic/search/deployments/123
*/
adminUrl: z
.string()
.url()
.describe(
'A private admin URL for managing the deployment. This URL is only accessible by project owners.'
)
})
.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.
@ -216,6 +276,28 @@ Deployments are private to a developer or team until they are published, at whic
)
.openapi('Deployment')
export function parseDeploymentSelectSchema(
deployment: Record<string, any>
): Deployment {
const { projectIdentifier, deploymentIdentifier } = parseDeploymentIdentifier(
deployment.identifier
)
return parseZodSchema(deploymentSelectSchema, {
...deployment,
gatewayBaseUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${deploymentIdentifier}`,
gatewayMcpUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${deploymentIdentifier}/mcp`,
marketplaceUrl: `${env.AGENTIC_WEB_BASE_URL}/marketplace/projects/${projectIdentifier}`,
adminUrl: `${env.AGENTIC_WEB_BASE_URL}/app/projects/${projectIdentifier}/deployments/${deployment.hash}`
})
}
export function parseDeploymentSelectArraySchema(
deployments: Record<string, any>[]
): Deployment[] {
return deployments.map(parseDeploymentSelectSchema)
}
export const deploymentAdminSelectSchema = deploymentSelectSchema
.extend({
origin: resolvedAgenticProjectConfigSchema.shape.origin,
@ -223,6 +305,15 @@ export const deploymentAdminSelectSchema = deploymentSelectSchema
})
.openapi('AdminDeployment')
export function parseDeploymentAdminSelectSchema(
deployment: Record<string, any>
): z.infer<typeof deploymentAdminSelectSchema> {
return parseZodSchema(deploymentAdminSelectSchema, {
...parseDeploymentSelectSchema(deployment),
...deployment
})
}
export const deploymentInsertSchema = agenticProjectConfigSchema.strict()
// TODO: Deployments should be immutable, so we should not allow updates aside

Wyświetl plik

@ -1,3 +1,4 @@
import { parseZodSchema } from '@agentic/platform-core'
import {
agenticProjectConfigSchema,
pricingIntervalSchema,
@ -20,6 +21,9 @@ import {
} from '@fisch0920/drizzle-orm/pg-core'
import { z } from '@hono/zod-openapi'
import { env } from '@/lib/env'
import type { Project } from '../types'
import {
deploymentIdSchema,
projectIdentifierSchema,
@ -293,13 +297,56 @@ export const projectSelectSchema = createSelectSchema(projects, {
.optional()
})
.strip()
// TODO
// .refine((project) => ({
// ...project,
// gatewayBaseUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${project.identifier}`,
// gatewayMcpUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${project.identifier}/mcp`,
// webBaseUrl: `${env.AGENTIC_WEB_BASE_URL}/marketplace/projects/${project.identifier}`
// }))
// These are all derived virtual URLs that are not stored in the database
.extend({
/**
* The public base HTTP URL for the project supporting HTTP POST requests for
* individual tools at `/tool-name` subpaths.
*
* @example https://gateway.agentic.com/@agentic/search
*/
gatewayBaseUrl: z
.string()
.url()
.describe(
'The public base HTTP URL for the project supporting HTTP POST requests for individual tools at `/tool-name` subpaths.'
),
/**
* The public MCP URL for the project supporting the Streamable HTTP transport.
*
* @example https://gateway.agentic.com/@agentic/search/mcp
*/
gatewayMcpUrl: z
.string()
.url()
.describe(
'The public MCP URL for the project supporting the Streamable HTTP transport.'
),
/**
* The public marketplace URL for the project.
*
* @example https://agentic.so/marketplace/projects/@agentic/search
*/
marketplaceUrl: z
.string()
.url()
.describe('The public marketplace URL for the project.'),
/**
* A private admin URL for managing the project. This URL is only accessible
* by project owners.
*
* @example https://agentic.so/app/projects/@agentic/search
*/
adminUrl: z
.string()
.url()
.describe(
'A private admin URL for managing the project. This URL is only accessible by project owners.'
)
})
.describe(
`A Project represents a single Agentic API product. It 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.
@ -309,6 +356,24 @@ Internally, Projects manage all of the Stripe billing resources across Deploymen
)
.openapi('Project')
export function parseProjectSelectSchema(
project: Record<string, any>
): Project {
return parseZodSchema(projectSelectSchema, {
...project,
gatewayBaseUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${project.identifier}`,
gatewayMcpUrl: `${env.AGENTIC_GATEWAY_BASE_URL}/${project.identifier}/mcp`,
marketplaceUrl: `${env.AGENTIC_WEB_BASE_URL}/marketplace/projects/${project.identifier}`,
adminUrl: `${env.AGENTIC_WEB_BASE_URL}/app/projects/${project.identifier}`
})
}
export function parseProjectSelectArraySchema(
projects: Record<string, any>[]
): Project[] {
return projects.map(parseProjectSelectSchema)
}
export const projectInsertSchema = createInsertSchema(projects, {
identifier: projectIdentifierSchema,

Wyświetl plik

@ -2,10 +2,10 @@ import { assert } from '@agentic/platform-core'
import type { RawProject } from '@/db'
export async function aclPublicProject(
export function aclPublicProject(
project: RawProject | undefined,
projectId?: string
) {
): asserts project {
assert(
project,
404,

Wyświetl plik

@ -72,7 +72,7 @@ export async function upsertConsumerStripeCheckout(
404,
`Project not found "${projectId}" for deployment "${deploymentId}"`
)
await aclPublicProject(project)
aclPublicProject(project)
// Validate the deployment only after we're sure the project is publicly
// accessible.

Wyświetl plik

@ -60,7 +60,7 @@ export async function upsertConsumer(
404,
`Project not found "${projectId}" for deployment "${deploymentId}"`
)
await aclPublicProject(project)
aclPublicProject(project)
// Validate the deployment only after we're sure the project is publicly
// accessible.

Wyświetl plik

@ -117,7 +117,7 @@
"name": {
"type": "string",
"minLength": 1,
"description": "Human-readable name for the pricing plan (eg, \"Free\", \"Starter Monthly\", \"Pro Annual\", etc)"
"description": "Display name for the pricing plan (eg, \"Free\", \"Starter Monthly\", \"Pro Annual\", etc)"
},
"slug": {
"type": "string",
@ -305,8 +305,7 @@
"type": "string",
"enum": [
"sum",
"count",
"last"
"count"
],
"default": "sum"
}

Wyświetl plik

@ -70,8 +70,11 @@ export default async function TheBestDamnLandingPageEver() {
<div className='flex flex-col gap-4 text-sm max-w-2xl text-center'>
<p>
This example uses the{' '}
<Link href='/marketplace/projects/@agentic/search' className='link'>
@agentic/search
<Link
href={`/marketplace/projects/${projectIdentifier}`}
className='link'
>
{projectIdentifier}
</Link>{' '}
tool to provide an LLM access to the web.
</p>

Wyświetl plik

@ -145,11 +145,11 @@ Every time you make a change to your project, you can run `agentic deploy` which
```json
{
"id": "depl_m2yl7dpdpc5xk8b3cwvuzkg3",
"createdAt": "2025-06-27 18:14:34.641308+00",
"updatedAt": "2025-06-27 18:14:34.641308+00",
"identifier": "@dev/search@b57dc301",
"hash": "b57dc301",
"id": "depl_kf4c3o8efh2y84dp5iwsha95",
"createdAt": "2025-06-28 05:38:34.960754+00",
"updatedAt": "2025-06-28 05:38:34.960754+00",
"identifier": "@dev/search@42ad78bf",
"hash": "42ad78bf",
"published": false,
"description": "Official Google Search tool. Useful for finding up-to-date news and information about any topic.",
"readme": "",
@ -160,6 +160,7 @@ Every time you make a change to your project, you can run `agentic deploy` which
"name": "search",
"description": "Uses Google Search to return the most relevant web pages for a given query. Useful for finding up-to-date news and information about any topic.",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"num": {
@ -186,10 +187,10 @@ Every time you make a change to your project, you can run `agentic deploy` which
}
},
"required": ["query"],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"news": {},
@ -201,8 +202,7 @@ Every time you make a change to your project, you can run `agentic deploy` which
"answerBox": {},
"knowledgeGraph": {}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
"additionalProperties": false
}
}
],
@ -284,7 +284,11 @@ Every time you make a change to your project, you can run `agentic deploy` which
"limit": 1000,
"mode": "approximate",
"enabled": true
}
},
"gatewayBaseUrl": "https://gateway.agentic.so/@dev/search@42ad78bf",
"gatewayMcpUrl": "https://gateway.agentic.so/@dev/search@42ad78bf/mcp",
"marketplaceUrl": "https://agentic.so/marketplace/projects/@dev/search",
"adminUrl": "https://agentic.so/app/projects/@dev/search/deployments/42ad78bf"
}
```
@ -292,9 +296,9 @@ Every time you make a change to your project, you can run `agentic deploy` which
## 5. Test your deployment
The easiest way to test your deployment is to visit it in your Agentic dashboard: `https://agentic.so/app/projects/<your-deployment-identifier>`.
The easiest way to test your deployment is to visit it in your Agentic admin dashboard. This will be the `adminUrl` in the returned deployment and should look like: `https://agentic.so/app/projects/<your-project-identifier>/deployments/<hash>`.
This page will show all the tools available on your deployment and includes a GUI for how to call them with various MCP clients, TS LLM SDKs, Python LLM SDKs, and raw HTTP.
This page shows you all the tools available for your deployment and includes a GUI showing how to call them with various MCP clients, TS LLM SDKs, Python LLM SDKs, and simple HTTP.
<Frame caption='Example of calling an Agentic tool'>
<img

Wyświetl plik

@ -147,11 +147,11 @@ Every time you make a change to your project, you can run `agentic deploy` which
```json
{
"id": "depl_m2yl7dpdpc5xk8b3cwvuzkg3",
"createdAt": "2025-06-27 18:14:34.641308+00",
"updatedAt": "2025-06-27 18:14:34.641308+00",
"identifier": "@dev/search@b57dc301",
"hash": "b57dc301",
"id": "depl_kf4c3o8efh2y84dp5iwsha95",
"createdAt": "2025-06-28 05:38:34.960754+00",
"updatedAt": "2025-06-28 05:38:34.960754+00",
"identifier": "@dev/search@42ad78bf",
"hash": "42ad78bf",
"published": false,
"description": "Official Google Search tool. Useful for finding up-to-date news and information about any topic.",
"readme": "",
@ -162,6 +162,7 @@ Every time you make a change to your project, you can run `agentic deploy` which
"name": "search",
"description": "Uses Google Search to return the most relevant web pages for a given query. Useful for finding up-to-date news and information about any topic.",
"inputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"num": {
@ -188,10 +189,10 @@ Every time you make a change to your project, you can run `agentic deploy` which
}
},
"required": ["query"],
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
"additionalProperties": false
},
"outputSchema": {
"$schema": "http://json-schema.org/draft-07/schema#",
"type": "object",
"properties": {
"news": {},
@ -203,8 +204,7 @@ Every time you make a change to your project, you can run `agentic deploy` which
"answerBox": {},
"knowledgeGraph": {}
},
"additionalProperties": false,
"$schema": "http://json-schema.org/draft-07/schema#"
"additionalProperties": false
}
}
],
@ -286,7 +286,11 @@ Every time you make a change to your project, you can run `agentic deploy` which
"limit": 1000,
"mode": "approximate",
"enabled": true
}
},
"gatewayBaseUrl": "https://gateway.agentic.so/@dev/search@42ad78bf",
"gatewayMcpUrl": "https://gateway.agentic.so/@dev/search@42ad78bf/mcp",
"marketplaceUrl": "https://agentic.so/marketplace/projects/@dev/search",
"adminUrl": "https://agentic.so/app/projects/@dev/search/deployments/42ad78bf"
}
```
@ -294,9 +298,9 @@ Every time you make a change to your project, you can run `agentic deploy` which
## 5. Test your deployment
The easiest way to test your deployment is to visit it in your Agentic dashboard: `https://agentic.so/app/projects/<your-deployment-identifier>`.
The easiest way to test your deployment is to visit it in your Agentic admin dashboard. This will be the `adminUrl` in the returned deployment and should look like: `https://agentic.so/app/projects/<your-project-identifier>/deployments/<hash>`.
This page will show all the tools available on your deployment and includes a GUI for how to call them with various MCP clients, TS LLM SDKs, Python LLM SDKs, and raw HTTP.
This page shows you all the tools available for your deployment and includes a GUI showing how to call them with various MCP clients, TS LLM SDKs, Python LLM SDKs, and simple HTTP.
<Frame caption='Example of calling an Agentic tool'>
<img

Wyświetl plik

@ -1,7 +1,7 @@
import { defineConfig } from '@agentic/platform'
export default defineConfig({
name: 'test-basic-mcp',
name: 'Test Basic MCP',
origin: {
type: 'mcp',
url: 'https://agentic-basic-mcp-test.onrender.com/mcp'

Wyświetl plik

@ -622,6 +622,26 @@ export interface components {
lastPublishedDeployment?: unknown;
lastDeployment?: unknown;
deployment?: unknown;
/**
* Format: uri
* @description The public base HTTP URL for the project supporting HTTP POST requests for individual tools at `/tool-name` subpaths.
*/
gatewayBaseUrl: string;
/**
* Format: uri
* @description The public MCP URL for the project supporting the Streamable HTTP transport.
*/
gatewayMcpUrl: string;
/**
* Format: uri
* @description The public marketplace URL for the project.
*/
marketplaceUrl: string;
/**
* Format: uri
* @description A private admin URL for managing the project. This URL is only accessible by project owners.
*/
adminUrl: string;
};
/** @description Public deployment identifier (e.g. "@namespace/project-slug@{hash|version|latest}") */
DeploymentIdentifier: string;
@ -687,7 +707,7 @@ export interface components {
};
};
/**
* @description Human-readable name for the pricing plan (eg, "Free", "Starter Monthly", "Pro Annual", etc)
* @description Display name for the pricing plan (eg, "Free", "Starter Monthly", "Pro Annual", etc)
* @example Starter Monthly
*/
name: string;
@ -722,7 +742,7 @@ export interface components {
tiers?: components["schemas"]["PricingPlanTier"][];
defaultAggregation?: {
/** @default sum */
formula: "sum" | "count" | "last";
formula: "sum" | "count";
};
transformQuantity?: {
divideBy: number;
@ -814,6 +834,26 @@ export interface components {
pricingIntervals: components["schemas"]["PricingInterval"][];
defaultRateLimit?: components["schemas"]["RateLimit"] & unknown;
project?: unknown;
/**
* Format: uri
* @description The public base HTTP URL for the deployment supporting HTTP POST requests for individual tools at `/tool-name` subpaths.
*/
gatewayBaseUrl: string;
/**
* Format: uri
* @description The public MCP URL for the deployment supporting the Streamable HTTP transport.
*/
gatewayMcpUrl: string;
/**
* Format: uri
* @description The public marketplace URL for the deployment's project.
*/
marketplaceUrl: string;
/**
* Format: uri
* @description A private admin URL for managing the deployment. This URL is only accessible by project owners.
*/
adminUrl: string;
};
TeamMember: {
createdAt: string;
@ -855,6 +895,11 @@ export interface components {
user?: components["schemas"]["User"];
project?: components["schemas"]["Project"];
deployment?: unknown;
/**
* Format: uri
* @description A private admin URL for managing the customer's subscription. This URL is only accessible by the customer.
*/
adminUrl: string;
};
/** @description Origin adapter is used to configure the origin API server downstream from Agentic's API gateway. It specifies whether the origin API server denoted by `url` is hosted externally or deployed internally to Agentic's infrastructure. It also specifies the format for how origin tools are defined: either an OpenAPI spec or an MCP server.
*

Wyświetl plik

@ -42,7 +42,6 @@
- simplify `AgenticToolClient` and only require one package per TS LLM SDK
- `createAISDKToolsFromIdentifier(projectIdentifier)`
- add really strict free rate-limits to `@agentic/search`
- add urls to db models (projects, deployments, consumers) so CLI users can easily debug
## TODO: Post-MVP