feat: add tool-client and search example; refactor e2e

pull/715/head
Travis Fischer 2025-06-24 07:34:22 -05:00
rodzic e388bcee46
commit cf4a404fd0
52 zmienionych plików z 2298 dodań i 277 usunięć

Wyświetl plik

@ -14,7 +14,8 @@ import {
import { deploymentIdentifierAndPopulateSchema } from './schemas'
const route = createRoute({
description: 'Gets a deployment by its public identifier',
description:
'Gets a deployment by its identifier (eg, "@username/project-name@latest").',
tags: ['deployments'],
operationId: 'getDeploymentByIdentifier',
method: 'get',

Wyświetl plik

@ -0,0 +1,64 @@
import type { DefaultHonoEnv } from '@agentic/platform-hono'
import { assert, parseZodSchema } from '@agentic/platform-core'
import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import { schema } from '@/db'
import { aclPublicProject } from '@/lib/acl-public-project'
import { tryGetDeploymentByIdentifier } from '@/lib/deployments/try-get-deployment-by-identifier'
import {
openapiAuthenticatedSecuritySchemas,
openapiErrorResponse404,
openapiErrorResponses
} from '@/lib/openapi-utils'
import { deploymentIdentifierAndPopulateSchema } from './schemas'
const route = createRoute({
description:
'Gets a public deployment by its identifier (eg, "@username/project-name@latest").',
tags: ['deployments'],
operationId: 'getPublicDeploymentByIdentifier',
method: 'get',
path: 'deployments/public/by-identifier',
security: openapiAuthenticatedSecuritySchemas,
request: {
query: deploymentIdentifierAndPopulateSchema
},
responses: {
200: {
description: 'A deployment object',
content: {
'application/json': {
schema: schema.deploymentSelectSchema
}
}
},
...openapiErrorResponses,
...openapiErrorResponse404
}
})
export function registerV1GetPublicDeploymentByIdentifier(
app: OpenAPIHono<DefaultHonoEnv>
) {
return app.openapi(route, async (c) => {
const { deploymentIdentifier, populate = [] } = c.req.valid('query')
const deployment = await tryGetDeploymentByIdentifier(c, {
deploymentIdentifier,
with: {
project: true,
...Object.fromEntries(populate.map((field) => [field, true]))
}
})
assert(deployment, 404, `Deployment not found "${deploymentIdentifier}"`)
assert(
deployment.project,
404,
`Project not found for deployment "${deploymentIdentifier}"`
)
await aclPublicProject(deployment.project!)
return c.json(parseZodSchema(schema.deploymentSelectSchema, deployment))
})
}

Wyświetl plik

@ -27,6 +27,7 @@ import { registerV1AdminGetDeploymentByIdentifier } from './deployments/admin-ge
import { registerV1CreateDeployment } from './deployments/create-deployment'
import { registerV1GetDeployment } from './deployments/get-deployment'
import { registerV1GetDeploymentByIdentifier } from './deployments/get-deployment-by-identifier'
import { registerV1GetPublicDeploymentByIdentifier } from './deployments/get-public-deployment-by-identifier'
import { registerV1ListDeployments } from './deployments/list-deployments'
import { registerV1PublishDeployment } from './deployments/publish-deployment'
import { registerV1UpdateDeployment } from './deployments/update-deployment'
@ -133,6 +134,7 @@ registerV1ListConsumers(privateRouter)
registerV1ListConsumersForProject(privateRouter)
// Deployments
registerV1GetPublicDeploymentByIdentifier(publicRouter)
registerV1GetDeploymentByIdentifier(privateRouter) // must be before `registerV1GetDeployment`
registerV1GetDeployment(privateRouter)
registerV1CreateDeployment(privateRouter)

Wyświetl plik

@ -248,9 +248,23 @@ export const projectSelectSchema = createSelectSchema(projects, {
return deploymentSelectSchema.parse(deployment)
})
.optional(),
// .openapi('Deployment', { type: 'object' }),
lastDeployment: z
.any()
.refine(
(deployment): boolean =>
!deployment || deploymentSelectSchema.safeParse(deployment).success,
{
message: 'Invalid lastDeployment'
}
)
.transform((deployment): any => {
if (!deployment) return undefined
return deploymentSelectSchema.parse(deployment)
})
.optional(),
deployment: z
.any()
.refine(
(deployment): boolean =>
@ -264,7 +278,6 @@ export const projectSelectSchema = createSelectSchema(projects, {
return deploymentSelectSchema.parse(deployment)
})
.optional()
// .openapi('Deployment', { type: 'object' })
})
.strip()
// TODO

Wyświetl plik

@ -28,9 +28,11 @@ export const users = pgTable(
username: username().unique().notNull(),
role: userRoleEnum().default('user').notNull(),
name: text(),
email: text().notNull().unique(),
isEmailVerified: boolean().default(false).notNull(),
name: text(),
bio: text(),
image: text(),
//isStripeConnectEnabledByDefault: boolean().default(true).notNull(),
@ -57,6 +59,7 @@ export const userSelectSchema = createSelectSchema(users)
export const userUpdateSchema = createUpdateSchema(users)
.pick({
name: true,
bio: true,
image: true
//isStripeConnectEnabledByDefault: true
})

Wyświetl plik

@ -1,7 +1,7 @@
import type { DefaultHonoContext } from '@agentic/platform-hono'
import { assert } from '@agentic/platform-core'
import { parseDeploymentIdentifier } from '@agentic/platform-validators'
import type { AuthenticatedHonoContext } from '@/lib/types'
import {
and,
db,
@ -12,6 +12,8 @@ import {
} from '@/db'
import { setPublicCacheControl } from '@/lib/cache-control'
import type { AuthenticatedHonoContext } from '../types'
/**
* Attempts to find the Deployment matching the given deployment ID or
* identifier.
@ -21,7 +23,7 @@ import { setPublicCacheControl } from '@/lib/cache-control'
* Does not take care of ACLs.
*/
export async function tryGetDeploymentByIdentifier(
ctx: AuthenticatedHonoContext,
ctx: AuthenticatedHonoContext | DefaultHonoContext,
{
deploymentIdentifier,
strict = false,

Wyświetl plik

@ -13,3 +13,6 @@ AGENTIC_GATEWAY_BASE_URL='http://localhost:8787'
AGENTIC_DEV_EMAIL=
AGENTIC_DEV_PASSWORD=
AGENTIC_DEV_ACCESS_TOKEN=
AGENTIC_AGENTIC_EMAIL=
AGENTIC_AGENTIC_PASSWORD=

Wyświetl plik

@ -1,9 +1,11 @@
import { deployFixtures } from '../src/project-fixtures'
import { deployProjects } from '../src/deploy-projects'
import { devClient } from '../src/dev-client'
import { fixtures } from '../src/dev-fixtures'
async function main() {
console.log('\n\nDeploying fixtures...\n\n')
console.log('\n\nDeploying dev fixtures...\n\n')
const deployments = await deployFixtures()
const deployments = await deployProjects(fixtures, { client: devClient })
console.log(JSON.stringify(deployments, null, 2))
// eslint-disable-next-line unicorn/no-process-exit

Wyświetl plik

@ -1,14 +1,19 @@
import { deployFixtures, publishDeployments } from '../src/project-fixtures'
import { deployProjects } from '../src/deploy-projects'
import { devClient } from '../src/dev-client'
import { fixtures } from '../src/dev-fixtures'
import { publishDeployments } from '../src/publish-deployments'
async function main() {
console.log('\n\nDeploying fixtures...\n\n')
console.log('\n\nDeploying dev fixtures...\n\n')
const deployments = await deployFixtures()
const deployments = await deployProjects(fixtures, { client: devClient })
console.log(JSON.stringify(deployments, null, 2))
console.log('\n\nPublishing deployments...\n\n')
console.log('\n\nPublishing dev fixture deployments...\n\n')
const publishedDeployments = await publishDeployments(deployments)
const publishedDeployments = await publishDeployments(deployments, {
client: devClient
})
console.log(JSON.stringify(publishedDeployments, null, 2))
// eslint-disable-next-line unicorn/no-process-exit

Wyświetl plik

@ -1,7 +1,10 @@
import { AgenticApiClient } from '@agentic/platform-api-client'
import { examples } from '../src/agentic-examples'
import { deployProjects } from '../src/deploy-projects'
import { fixtures } from '../src/dev-fixtures'
import { env, isProd } from '../src/env'
import { deployFixtures, publishDeployments } from '../src/project-fixtures'
import { publishDeployments } from '../src/publish-deployments'
export const client = new AgenticApiClient({
apiBaseUrl: env.AGENTIC_API_BASE_URL
@ -10,28 +13,55 @@ export const client = new AgenticApiClient({
async function main() {
// TODO: clear existing tables? and include prompt to double check if so...
console.log('\n\nCreating dev user...\n\n')
{
console.log('\n\nCreating "dev" user...\n\n')
const devAuthSession = await client.signUpWithPassword({
username: 'dev',
email: env.AGENTIC_DEV_EMAIL,
password: env.AGENTIC_DEV_PASSWORD
})
console.log(JSON.stringify(devAuthSession, null, 2))
const devAuthSession = await client.signUpWithPassword({
username: 'dev',
email: env.AGENTIC_DEV_EMAIL,
password: env.AGENTIC_DEV_PASSWORD
})
console.log(JSON.stringify(devAuthSession, null, 2))
console.warn(
`\n\nREMEMBER TO UPDATE "AGENTIC_DEV_ACCESS_TOKEN" in e2e/.env${isProd ? '.production' : ''}\n\n`
)
console.warn(
`\n\nREMEMBER TO UPDATE "AGENTIC_DEV_ACCESS_TOKEN" in e2e/.env${isProd ? '.production' : ''}\n\n`
)
console.log('\n\nDeploying fixtures...\n\n')
console.log('\n\nDeploying dev fixtures...\n\n')
const deployments = await deployFixtures()
console.log(JSON.stringify(deployments, null, 2))
const deployments = await deployProjects(fixtures, { client })
console.log(JSON.stringify(deployments, null, 2))
console.log('\n\nPublishing deployments...\n\n')
console.log('\n\nPublishing dev fixture deployments...\n\n')
const publishedDeployments = await publishDeployments(deployments)
console.log(JSON.stringify(publishedDeployments, null, 2))
const publishedDeployments = await publishDeployments(deployments, {
client
})
console.log(JSON.stringify(publishedDeployments, null, 2))
}
{
console.log('\n\nCreating "agentic" user...\n\n')
const agenticAuthSession = await client.signUpWithPassword({
username: 'agentic',
email: env.AGENTIC_AGENTIC_EMAIL,
password: env.AGENTIC_AGENTIC_PASSWORD
})
console.log(JSON.stringify(agenticAuthSession, null, 2))
console.log('\n\nDeploying example projects...\n\n')
const deployments = await deployProjects(examples, { client })
console.log(JSON.stringify(deployments, null, 2))
console.log('\n\nPublishing example project deployments...\n\n')
const publishedDeployments = await publishDeployments(deployments, {
client
})
console.log(JSON.stringify(publishedDeployments, null, 2))
}
// eslint-disable-next-line unicorn/no-process-exit
process.exit(0)

Wyświetl plik

@ -0,0 +1,17 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const exampleProjectNames = ['search']
const examplesDir = path.join(
fileURLToPath(import.meta.url),
'..',
'..',
'..',
'..',
'examples'
)
export const examples = exampleProjectNames.map((name) =>
path.join(examplesDir, name)
)

Wyświetl plik

@ -0,0 +1,30 @@
import type { AgenticApiClient } from '@agentic/platform-api-client'
import { loadAgenticConfig } from '@agentic/platform'
import pMap from 'p-map'
export async function deployProjects(
projects: string[],
{
client,
concurrency = 1
}: {
client: AgenticApiClient
concurrency?: number
}
) {
const deployments = await pMap(
projects,
async (project) => {
const config = await loadAgenticConfig({ cwd: project })
const deployment = await client.createDeployment(config)
console.log(`Deployed ${project} => ${deployment.identifier}`)
return deployment
},
{
concurrency
}
)
return deployments
}

Wyświetl plik

@ -2,7 +2,7 @@ import { AgenticApiClient } from '@agentic/platform-api-client'
import { env } from './env'
export const client = new AgenticApiClient({
export const devClient = new AgenticApiClient({
apiBaseUrl: env.AGENTIC_API_BASE_URL,
apiKey: env.AGENTIC_DEV_ACCESS_TOKEN
})

Wyświetl plik

@ -0,0 +1,31 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
const fixtureNames = [
// TODO: re-add these
// 'basic-raw-free-ts',
// 'basic-raw-free-json',
// 'pricing-freemium',
// 'pricing-pay-as-you-go',
// 'pricing-3-plans',
// 'pricing-monthly-annual',
// 'pricing-custom-0',
'basic-openapi',
'basic-mcp',
'everything-openapi'
]
const fixturesDir = path.join(
fileURLToPath(import.meta.url),
'..',
'..',
'..',
'..',
'packages',
'fixtures'
)
const validFixturesDir = path.join(fixturesDir, 'valid')
export const fixtures = fixtureNames.map((name) =>
path.join(validFixturesDir, name)
)

Wyświetl plik

@ -14,9 +14,12 @@ export const envSchema = z.object({
AGENTIC_API_BASE_URL: z.string().url().optional(),
AGENTIC_DEV_ACCESS_TOKEN: z.string().nonempty(),
AGENTIC_DEV_EMAIL: z.string().email(),
AGENTIC_DEV_PASSWORD: z.string().nonempty(),
AGENTIC_DEV_ACCESS_TOKEN: z.string().nonempty(),
AGENTIC_AGENTIC_EMAIL: z.string().email(),
AGENTIC_AGENTIC_PASSWORD: z.string().nonempty(),
AGENTIC_GATEWAY_BASE_URL: z
.string()

Wyświetl plik

@ -1,97 +0,0 @@
import path from 'node:path'
import { fileURLToPath } from 'node:url'
import type { Deployment } from '@agentic/platform-types'
import { loadAgenticConfig } from '@agentic/platform'
import { assert } from '@agentic/platform-core'
import pMap from 'p-map'
import semver from 'semver'
import { client } from './client'
const fixtures = [
// TODO: re-add these
// 'basic-raw-free-ts',
// 'basic-raw-free-json',
// 'pricing-freemium',
// 'pricing-pay-as-you-go',
// 'pricing-3-plans',
// 'pricing-monthly-annual',
// 'pricing-custom-0',
'basic-openapi',
'basic-mcp',
'everything-openapi'
]
const fixturesDir = path.join(
fileURLToPath(import.meta.url),
'..',
'..',
'..',
'..',
'packages',
'fixtures'
)
const validFixturesDir = path.join(fixturesDir, 'valid')
export async function deployFixtures({
concurrency = 1
}: {
concurrency?: number
} = {}) {
const deployments = await pMap(
fixtures,
async (fixture) => {
const fixtureDir = path.join(validFixturesDir, fixture)
const config = await loadAgenticConfig({ cwd: fixtureDir })
const deployment = await client.createDeployment(config)
console.log(`Deployed ${fixture} => ${deployment.identifier}`)
return deployment
},
{
concurrency
}
)
return deployments
}
export async function publishDeployments(
deployments: Deployment[],
{
concurrency = 1
}: {
concurrency?: number
} = {}
) {
const publishedDeployments = await pMap(
deployments,
async (deployment) => {
const project = await client.getProject({
projectId: deployment.projectId,
populate: ['lastDeployment']
})
const baseVersion = project.lastPublishedDeploymentVersion || '0.0.0'
const version = semver.inc(baseVersion, 'patch')
assert(version, `Failed to increment deployment version "${baseVersion}"`)
const publishedDeployment = await client.publishDeployment(
{ version },
{
deploymentId: deployment.id
}
)
console.log(`Published ${deployment.identifier} => ${version}`)
return publishedDeployment
},
{
concurrency
}
)
return publishedDeployments
}

Wyświetl plik

@ -0,0 +1,45 @@
import type { AgenticApiClient } from '@agentic/platform-api-client'
import type { Deployment } from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
import pMap from 'p-map'
import semver from 'semver'
export async function publishDeployments(
deployments: Deployment[],
{
client,
concurrency = 1
}: {
client: AgenticApiClient
concurrency?: number
}
) {
const publishedDeployments = await pMap(
deployments,
async (deployment) => {
const project = await client.getProject({
projectId: deployment.projectId,
populate: ['lastDeployment']
})
const baseVersion = project.lastPublishedDeploymentVersion || '0.0.0'
const version = semver.inc(baseVersion, 'patch')
assert(version, `Failed to increment deployment version "${baseVersion}"`)
const publishedDeployment = await client.publishDeployment(
{ version },
{
deploymentId: deployment.id
}
)
console.log(`Published ${deployment.identifier} => ${version}`)
return publishedDeployment
},
{
concurrency
}
)
return publishedDeployments
}

Wyświetl plik

@ -12,11 +12,6 @@
},
"type": "module",
"source": "./src/worker.ts",
"exports": {
".": {
"import": "./src/worker.ts"
}
},
"scripts": {
"deploy": "wrangler deploy --outdir dist --upload-source-maps --var SENTRY_RELEASE:$(sentry-cli releases propose-version)",
"dev": "wrangler dev",
@ -35,7 +30,6 @@
"@agentic/platform-hono": "workspace:*",
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",
"@cloudflare/workers-oauth-provider": "^0.0.5",
"@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "catalog:",
"@sentry/cloudflare": "catalog:",

Wyświetl plik

@ -1,3 +1,4 @@
import type { AgenticMcpRequestMetadata } from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
import { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js'
import { StreamableHTTPClientTransport } from '@modelcontextprotocol/sdk/client/streamableHttp.js'
@ -5,7 +6,6 @@ import * as Sentry from '@sentry/cloudflare'
import { DurableObject } from 'cloudflare:workers'
import type { RawEnv } from './env'
import type { AgenticMcpRequestMetadata } from './types'
export type DurableMcpClientInfo = {
url: string

Wyświetl plik

@ -1,5 +1,6 @@
import type {
AdminDeployment,
AgenticMcpRequestMetadata,
PricingPlan,
Tool
} from '@agentic/platform-types'
@ -11,7 +12,6 @@ import type { DurableMcpClientBase } from './durable-mcp-client'
import type { RawEnv } from './env'
import type {
AdminConsumer,
AgenticMcpRequestMetadata,
CacheStatus,
McpToolCallResponse,
RateLimitResult,

Wyświetl plik

@ -112,47 +112,3 @@ export type ResolvedOriginToolCallResult = {
toolCallResponse: McpToolCallResponse
}
)
export type AgenticMcpRequestMetadata = {
agenticProxySecret: string
sessionId: string
isCustomerSubscriptionActive: boolean
customerId?: string
customerSubscriptionStatus?: string
customerSubscriptionPlan?: string
userId?: string
userEmail?: string
userUsername?: string
userName?: string
userCreatedAt?: string
userUpdatedAt?: string
deploymentId: string
deploymentIdentifier: string
projectId: string
projectIdentifier: string
ip?: string
} & (
| {
// If the customer has an active subscription, these fields are guaranteed
// to be present in the metadata.
isCustomerSubscriptionActive: true
customerId: string
customerSubscriptionStatus: string
userId: string
userEmail: string
userUsername: string
userCreatedAt: string
userUpdatedAt: string
}
| {
// If the customer does not have an active subscription, then the customer
// fields may or may not be present.
isCustomerSubscriptionActive: false
}
)

Wyświetl plik

@ -1,4 +1,5 @@
NEXT_PUBLIC_API_BASE_URL=
NEXT_PUBLIC_AGENTIC_API_BASE_URL=
NEXT_PUBLIC_AGENTIC_GATEWAY_BASE_URL=
NEXT_PUBLIC_STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=

Wyświetl plik

@ -30,6 +30,7 @@
"@radix-ui/react-dropdown-menu": "^2.1.15",
"@radix-ui/react-label": "^2.1.7",
"@radix-ui/react-slot": "^1.2.3",
"@radix-ui/react-tabs": "^1.1.12",
"@radix-ui/react-tooltip": "^1.2.7",
"@react-three/cannon": "^6.6.0",
"@react-three/drei": "^10.2.0",
@ -43,6 +44,7 @@
"canvas-confetti": "^1.9.3",
"class-variance-authority": "^0.7.1",
"clsx": "^2.1.1",
"hast-util-to-jsx-runtime": "^2.3.6",
"ky": "catalog:",
"lucide-react": "^0.518.0",
"motion": "^12.18.1",
@ -54,6 +56,7 @@
"react-infinite-scroll-hook": "^6.0.1",
"react-lottie-player": "^2.1.0",
"react-use": "^17.6.0",
"shiki": "^3.7.0",
"sonner": "^2.0.5",
"stripe": "catalog:",
"suspend-react": "^0.1.3",

Wyświetl plik

@ -0,0 +1,202 @@
'use client'
import { useLocalStorage } from 'react-use'
import { CodeBlock } from '@/components/code-block'
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs'
import {
defaultConfig,
type DeveloperConfig,
type HTTPTarget,
httpTargetLabels,
httpTargets,
type MCPClientTarget,
mcpClientTargetLabels,
mcpClientTargets,
type PyFrameworkTarget,
pyFrameworkTargetLabels,
pyFrameworkTargets,
type Target,
targetLabels,
targets,
type TsFrameworkTarget,
tsFrameworkTargetLabels,
tsFrameworkTargets
} from '@/lib/developer-config'
export function ExampleUsage() {
const [config, setConfig] = useLocalStorage<DeveloperConfig>(
'config',
defaultConfig
)
return (
<Tabs
defaultValue={config!.target}
onValueChange={(value) =>
setConfig({
...defaultConfig,
...config,
target: value as Target
})
}
className='w-full max-w-2xl'
>
<TabsList>
{targets.map((target) => (
<TabsTrigger key={target} value={target} className='cursor-pointer'>
{targetLabels[target]}
</TabsTrigger>
))}
</TabsList>
<TabsContent value='mcp' className='w-full'>
<Tabs
defaultValue={config!.mcpClientTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
...config,
mcpClientTarget: value as MCPClientTarget
})
}
className='w-full'
>
<TabsList className='h-auto flex-wrap'>
{mcpClientTargets.map((mcpClientTarget) => (
<TabsTrigger
key={mcpClientTarget}
value={mcpClientTarget}
className='cursor-pointer'
>
{mcpClientTargetLabels[mcpClientTarget]}
</TabsTrigger>
))}
</TabsList>
{mcpClientTargets.map((mcpClientTarget) => (
<TabsContent
key={mcpClientTarget}
value={mcpClientTarget}
className='w-full'
>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='json'
className='p-4 rounded-sm w-full'
/>
</TabsContent>
))}
</Tabs>
</TabsContent>
<TabsContent value='typescript' className='w-full'>
<Tabs
defaultValue={config!.tsFrameworkTarget ?? 'ai'}
onValueChange={(value) =>
setConfig({
...defaultConfig,
...config,
tsFrameworkTarget: value as TsFrameworkTarget
})
}
className='w-full'
>
<TabsList className='h-auto flex-wrap'>
{tsFrameworkTargets.map((framework) => (
<TabsTrigger
key={framework}
value={framework}
className='cursor-pointer'
>
{tsFrameworkTargetLabels[framework]}
</TabsTrigger>
))}
</TabsList>
{tsFrameworkTargets.map((framework) => (
<TabsContent key={framework} value={framework} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='ts'
className='p-4 rounded-sm w-full'
/>
</TabsContent>
))}
</Tabs>
</TabsContent>
<TabsContent value='python' className='w-full'>
<Tabs
defaultValue={config!.pyFrameworkTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
...config,
pyFrameworkTarget: value as PyFrameworkTarget
})
}
className='w-full'
>
<TabsList className='h-auto flex-wrap'>
{pyFrameworkTargets.map((framework) => (
<TabsTrigger
key={framework}
value={framework}
className='cursor-pointer'
>
{pyFrameworkTargetLabels[framework]}
</TabsTrigger>
))}
</TabsList>
{pyFrameworkTargets.map((framework) => (
<TabsContent key={framework} value={framework} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='py'
className='p-4 rounded-sm w-full'
/>
</TabsContent>
))}
</Tabs>
</TabsContent>
<TabsContent value='http' className='w-full'>
<Tabs
defaultValue={config!.httpTarget}
onValueChange={(value) =>
setConfig({
...defaultConfig,
...config,
httpTarget: value as HTTPTarget
})
}
className='w-full'
>
<TabsList className='h-auto flex-wrap'>
{httpTargets.map((httpTarget) => (
<TabsTrigger
key={httpTarget}
value={httpTarget}
className='cursor-pointer'
>
{httpTargetLabels[httpTarget]}
</TabsTrigger>
))}
</TabsList>
{httpTargets.map((httpTarget) => (
<TabsContent key={httpTarget} value={httpTarget} className='w-full'>
<CodeBlock
code={JSON.stringify(config, null, 2)}
lang='bash'
className='p-4 rounded-sm w-full'
/>
</TabsContent>
))}
</Tabs>
</TabsContent>
</Tabs>
)
}

Wyświetl plik

@ -4,6 +4,8 @@ import { DemandSideCTA } from '@/components/demand-side-cta'
import { GitHubStarCounter } from '@/components/github-star-counter'
import { githubUrl, twitterUrl } from '@/lib/config'
import { ExampleUsage } from './example-usage'
export default function TheBestDamnLandingPageEver() {
return (
<>
@ -35,7 +37,7 @@ export default function TheBestDamnLandingPageEver() {
How It Works
</h2>
<div>TODO</div>
<ExampleUsage />
</section>
{/* Marketplace section */}

Wyświetl plik

@ -0,0 +1,60 @@
import { toJsxRuntime } from 'hast-util-to-jsx-runtime'
import { Fragment, type JSX, useEffect, useState } from 'react'
import { jsx, jsxs } from 'react/jsx-runtime'
import { type BundledLanguage, codeToHast } from 'shiki/bundle/web'
import { LoadingIndicator } from './loading-indicator'
export async function highlight({
code,
lang = 'ts',
theme = 'github-dark',
className
}: {
code: string
lang?: BundledLanguage
theme?: string
className?: string
}) {
const out = await codeToHast(code, {
lang,
theme,
transformers: [
{
pre(node) {
if (className) {
this.addClassToHast(node, className)
}
}
}
]
})
return toJsxRuntime(out, {
Fragment,
jsx,
jsxs
}) as JSX.Element
}
export function CodeBlock({
initial,
code,
lang,
theme,
className
}: {
initial?: JSX.Element
code: string
lang?: BundledLanguage
theme?: string
className?: string
}) {
const [nodes, setNodes] = useState(initial)
useEffect(() => {
void highlight({ code, lang, theme, className }).then(setNodes)
}, [code, lang, theme, className])
return nodes ?? <LoadingIndicator />
}

Wyświetl plik

@ -15,7 +15,7 @@ export function DemandSideCTA() {
<Button variant='outline' asChild className='h-full'>
<Link href={docsMarketplaceUrl} className='font-mono'>
readDocs();
readTheDocs();
</Link>
</Button>
</div>

Wyświetl plik

@ -0,0 +1,66 @@
'use client'
import * as TabsPrimitive from '@radix-ui/react-tabs'
import * as React from 'react'
import { cn } from '@/lib/utils'
function Tabs({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Root>) {
return (
<TabsPrimitive.Root
data-slot='tabs'
className={cn('flex flex-col gap-2', className)}
{...props}
/>
)
}
function TabsList({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.List>) {
return (
<TabsPrimitive.List
data-slot='tabs-list'
className={cn(
'bg-muted text-muted-foreground inline-flex h-9 w-fit items-center justify-center rounded-lg p-[3px]',
className
)}
{...props}
/>
)
}
function TabsTrigger({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Trigger>) {
return (
<TabsPrimitive.Trigger
data-slot='tabs-trigger'
className={cn(
"data-[state=active]:bg-background dark:data-[state=active]:text-foreground focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:outline-ring dark:data-[state=active]:border-input dark:data-[state=active]:bg-input/30 text-foreground dark:text-muted-foreground inline-flex h-[calc(100%-1px)] flex-1 items-center justify-center gap-1.5 rounded-md border border-transparent px-2 py-1 text-sm font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1 disabled:pointer-events-none disabled:opacity-50 data-[state=active]:shadow-sm [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className
)}
{...props}
/>
)
}
function TabsContent({
className,
...props
}: React.ComponentProps<typeof TabsPrimitive.Content>) {
return (
<TabsPrimitive.Content
data-slot='tabs-content'
className={cn('flex-1 outline-none', className)}
{...props}
/>
)
}
export { Tabs, TabsContent,TabsList, TabsTrigger }

Wyświetl plik

@ -59,7 +59,8 @@ export const url = isDev ? `http://localhost:${port}` : prodUrl
export const vercelUrl =
process.env.VERCEL_URL ?? process.env.NEXT_PUBLIC_VERCEL_URL
// export const webBaseUrl = isDev || !vercelUrl ? url : `https://${vercelUrl}`
export const apiBaseUrl = process.env.NEXT_PUBLIC_API_BASE_URL!
export const apiBaseUrl = process.env.NEXT_PUBLIC_AGENTIC_API_BASE_URL!
export const gatewayBaseUrl = process.env.NEXT_PUBLIC_AGENTIC_GATEWAY_BASE_URL!
export const posthogKey = process.env.NEXT_PUBLIC_POSTHOG_KEY!
export const posthogHost =

Wyświetl plik

@ -0,0 +1,214 @@
import type { Deployment, Project } from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
import { gatewayBaseUrl } from './config'
export const targetLabels = {
mcp: 'MCP',
typescript: 'TypeScript',
python: 'Python',
http: 'HTTP'
} as const
export const targets: (keyof typeof targetLabels)[] = Object.keys(
targetLabels
) as any
export type Target = (typeof targets)[number]
export const httpTargetLabels = {
curl: 'cURL',
httpie: 'HTTPie'
} as const
export const httpTargets: (keyof typeof httpTargetLabels)[] = Object.keys(
httpTargetLabels
) as any
export type HTTPTarget = (typeof httpTargets)[number]
export const mcpClientTargetLabels = {
any: 'Any MCP Client',
'claude-desktop': 'Claude Desktop',
raycast: 'Raycast',
cursor: 'Cursor',
windsurf: 'Windsurf',
cline: 'Cline',
goose: 'Goose'
} as const
export const mcpClientTargets: (keyof typeof mcpClientTargetLabels)[] =
Object.keys(mcpClientTargetLabels) as any
export type MCPClientTarget = (typeof mcpClientTargets)[number]
export const tsFrameworkTargetLabels = {
ai: 'Vercel AI SDK',
'openai-chat': 'OpenAI Chat',
'openai-responses': 'OpenAI Responses',
langchain: 'LangChain',
mastra: 'Mastra',
llamaindex: 'LlamaIndex',
'firebase-genkit': 'Firebase GenKit',
xsai: 'xsAI'
} as const
export const tsFrameworkTargets: (keyof typeof tsFrameworkTargetLabels)[] =
Object.keys(tsFrameworkTargetLabels) as any
export type TsFrameworkTarget = (typeof tsFrameworkTargets)[number]
export const pyFrameworkTargetLabels = {
openai: 'OpenAI',
langchain: 'LangChain',
llamaindex: 'LlamaIndex'
} as const
export const pyFrameworkTargets: (keyof typeof pyFrameworkTargetLabels)[] =
Object.keys(pyFrameworkTargetLabels) as any
export type PyFrameworkTarget = (typeof pyFrameworkTargets)[number]
export type DeveloperConfig = {
target: Target
mcpClientTarget: MCPClientTarget
tsFrameworkTarget: TsFrameworkTarget
pyFrameworkTarget: PyFrameworkTarget
httpTarget: HTTPTarget
}
export const defaultConfig: DeveloperConfig = {
target: 'typescript',
mcpClientTarget: 'any',
tsFrameworkTarget: 'ai',
pyFrameworkTarget: 'openai',
httpTarget: 'curl'
}
export function getCodeForDeveloperConfig(opts: {
config: DeveloperConfig
project: Project
deployment: Deployment
identifier: string
tool?: string
}): string {
const { config } = opts
switch (config.target) {
case 'mcp':
return getCodeForMCPClientConfig(opts)
case 'typescript':
return getCodeForTSFrameworkConfig(opts)
case 'python':
return 'Python support is coming soon...'
// return getCodeForPythonFrameworkConfig(opts)
case 'http':
return getCodeForHTTPConfig(opts)
}
}
export function getCodeForMCPClientConfig({
identifier
}: {
identifier: string
}): string {
return `${gatewayBaseUrl}/${identifier}/mcp`
}
export function getCodeForTSFrameworkConfig({
config,
identifier
}: {
config: DeveloperConfig
identifier: string
}): string {
switch (config.tsFrameworkTarget) {
case 'ai':
return `
import { createAISDKTools } from '@agentic/ai'
import { AgenticToolClient } from '@agentic/platform-tool-client'
import { openai } from '@ai-sdk/openai'
import { generateText } from 'ai'
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const result = await generateText({
model: openai('gpt-4o-mini'),
tools: createAISDKTools(searchTool),
toolChoice: 'required',
temperature: 0,
system: 'You are a helpful assistant. Be as concise as possible.',
prompt: 'What is the latest news about AI?'
})
console.log(result.toolResults[0])
`.trim()
case 'openai-chat':
return `
import { AgenticToolClient } from '@agentic/platform-tool-client'
import OpenAI from 'openai'
const openai = new OpenAI()
const searchTool = await AgenticToolClient.fromIdentifier('${identifier}')
const res = await openai.chat.completions.create({
messages: [
{
role: 'system',
content: 'You are a helpful assistant. Be as concise as possible.'
}
{
role: 'user',
content: 'What is the latest news about AI?'
}
],
model: 'gpt-4o-mini',
temperature: 0,
tools: searchTool.functions.toolSpecs,
tool_choice: 'required'
})
const message = res.choices[0]!.message!
const toolCall = message.tool_calls![0]!
const tool = searchTool.functions.get(toolCall.function.name)!
const toolResult = await tool(toolCall.function.arguments)
console.log(toolResult)
`.trim()
}
return ''
}
// export function getCodeForPythonFrameworkConfig({
// config,
// project,
// deployment,
// tool
// }: {
// config: DeveloperConfig
// project: Project
// deployment: Deployment
// identifier: string
// tool?: string
// }): string {
// return ''
// }
export function getCodeForHTTPConfig({
config,
identifier,
deployment,
tool
}: {
config: DeveloperConfig
deployment: Deployment
identifier: string
tool?: string
}): string {
tool ??= deployment.tools[0]?.name
assert(tool, 'tool is required')
// TODO: need a way of getting example tool args
const url = `${gatewayBaseUrl}/${identifier}/${tool}`
switch (config.httpTarget) {
case 'curl':
return `curl -X POST -H "Content-Type: application/json" -d '{"query": "example google search"}' ${url}`
case 'httpie':
return `http -j ${url} query='example google search'`
}
}

Wyświetl plik

@ -24,7 +24,8 @@ export default [
files: [
'packages/cli/src/**/*.ts',
'**/*.test.ts',
'packages/fixtures/valid/**/*.ts'
'packages/fixtures/valid/**/*.ts',
'examples/**/*.ts'
],
rules: {
'no-console': 'off',

Wyświetl plik

@ -0,0 +1,3 @@
SERPER_API_KEY=
AGENTIC_PROXY_SECRET=

Wyświetl plik

@ -0,0 +1 @@
MCP_ORIGIN_URL=

Wyświetl plik

@ -0,0 +1,17 @@
import 'dotenv/config'
import { defineConfig } from '@agentic/platform'
export default defineConfig({
name: 'search',
originUrl: process.env.MCP_ORIGIN_URL!,
originAdapter: {
type: 'mcp'
},
toolConfigs: [
{
name: 'search',
cacheControl: 'public, max-age=60, s-maxage=60 stale-while-revalidate=10'
}
]
})

Wyświetl plik

@ -0,0 +1,32 @@
{
"name": "@agentic/examples-search",
"private": true,
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "AGPL-3.0",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git",
"directory": "examples/search"
},
"type": "module",
"source": "./src/worker.ts",
"scripts": {
"dev": "wrangler dev",
"deploy": "wrangler deploy --outdir dist --upload-source-maps",
"test": "run-s test:*",
"test:typecheck": "tsc --noEmit"
},
"dependencies": {
"@agentic/platform": "workspace:*",
"@agentic/serper": "^7.6.7",
"@hono/mcp": "^0.1.0",
"@modelcontextprotocol/sdk": "catalog:",
"dotenv": "^16.5.0",
"hono": "catalog:",
"zod": "catalog:"
},
"devDependencies": {
"@cloudflare/workers-types": "catalog:",
"wrangler": "catalog:"
}
}

Wyświetl plik

@ -0,0 +1,11 @@
import { z } from 'zod'
export const envSchema = z.object({
SERPER_API_KEY: z.string().nonempty()
})
export type Env = z.infer<typeof envSchema>
export function parseEnv(inputEnv: Record<string, unknown>): Env {
return envSchema.parse(inputEnv)
}

Wyświetl plik

@ -0,0 +1,80 @@
import { SerperClient } from '@agentic/serper'
import { StreamableHTTPTransport } from '@hono/mcp'
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { Hono } from 'hono'
import { z } from 'zod'
import { type Env, parseEnv } from './env'
const app = new Hono()
const mcpServer = new McpServer({
name: 'search',
version: '1.0.0'
})
let serper: SerperClient
mcpServer.registerTool(
'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: z.object({
query: z.string().describe('Search query'),
num: z
.number()
.int()
.default(5)
.optional()
.describe('Number of results to return'),
type: z
.enum(['search', 'images', 'videos', 'places', 'news', 'shopping'])
.default('search')
.optional()
.describe('Type of Google search to perform')
}).shape,
outputSchema: z.object({}).passthrough().shape
},
async (args, { _meta }) => {
// (_meta.agentic as any).agenticProxySecret
const result: any = await serper!.search({
q: args.query,
num: args.num,
type: args.type
})
delete result.topStories
delete result.peopleAlsoAsk
delete result.searchParameters
delete result.credits
return {
content: [],
structuredContent: result
}
}
)
app.all('/mcp', async (c) => {
const transport = new StreamableHTTPTransport()
await mcpServer.connect(transport)
return transport.handleRequest(c)
})
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const parsedEnv = parseEnv(env)
if (!serper) {
serper = new SerperClient({ apiKey: parsedEnv.SERPER_API_KEY })
}
return app.fetch(request, parsedEnv, ctx)
}
} satisfies ExportedHandler<Env>

Wyświetl plik

@ -0,0 +1,8 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"compilerOptions": {
"types": ["@cloudflare/workers-types"]
},
"include": ["src", "*.config.ts"],
"exclude": ["node_modules", "dist"]
}

Wyświetl plik

@ -0,0 +1,12 @@
/**
* https://developers.cloudflare.com/workers/wrangler/configuration/
*/
{
"$schema": "node_modules/wrangler/config-schema.json",
"name": "agentic-mcp-search",
"main": "src/worker.ts",
"compatibility_date": "2025-05-25",
"compatibility_flags": ["nodejs_compat"],
"placement": { "mode": "smart" },
"upload_source_maps": true
}

Wyświetl plik

@ -7,14 +7,14 @@
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git"
},
"packageManager": "pnpm@10.12.1",
"packageManager": "pnpm@10.12.2",
"engines": {
"node": ">=22"
},
"type": "module",
"scripts": {
"build": "turbo build --filter=!web",
"dev": "turbo dev --continue",
"dev": "turbo dev --continue --filter=!./examples/*",
"clean": "turbo clean",
"fix": "run-s fix:*",
"fix:format": "prettier --write \"**/*.{js,ts,tsx}\"",

Wyświetl plik

@ -37,7 +37,7 @@ export class AgenticApiClient {
apiKey?: string
ky?: KyInstance
onUpdateAuth?: OnUpdateAuthSessionFunction
}) {
} = {}) {
assert(apiBaseUrl, 'AgenticApiClient missing required "apiBaseUrl"')
this.apiBaseUrl = apiBaseUrl
@ -655,7 +655,7 @@ export class AgenticApiClient {
.json()
}
/** Gets a deployment by its public identifier. */
/** Gets a deployment by its identifier. */
async getDeploymentByIdentifier<
TPopulate extends NonNullable<
OperationParameters<'getDeploymentByIdentifier'>['populate']
@ -672,6 +672,23 @@ export class AgenticApiClient {
.json()
}
/** Gets a public deployment by its identifier. */
async getPublicDeploymentByIdentifier<
TPopulate extends NonNullable<
OperationParameters<'getPublicDeploymentByIdentifier'>['populate']
>[number]
>(
searchParams: OperationParameters<'getPublicDeploymentByIdentifier'> & {
populate?: TPopulate[]
}
): Promise<PopulateDeployment<TPopulate>> {
return this.ky
.get(`v1/deployments/public/by-identifier`, {
searchParams: sanitizeSearchParams(searchParams)
})
.json()
}
/** Updates a deployment. */
async updateDeployment(
deployment: OperationBody<'updateDeployment'>,

Wyświetl plik

@ -22,20 +22,17 @@
"test:unit": "vitest run"
},
"dependencies": {
"@agentic/core": "^7.6.7",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-openapi-utils": "workspace:*",
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",
"@hono/zod-openapi": "catalog:",
"@modelcontextprotocol/sdk": "catalog:",
"semver": "catalog:",
"unconfig": "catalog:",
"zod": "catalog:"
"unconfig": "catalog:"
},
"devDependencies": {
"@types/semver": "catalog:",
"restore-cursor": "catalog:",
"zod-to-json-schema": "catalog:"
"@types/semver": "catalog:"
},
"publishConfig": {
"access": "public"

Wyświetl plik

@ -0,0 +1,32 @@
{
"name": "@agentic/platform-tool-client",
"version": "0.1.0",
"description": "Tool client for the Agentic platform.",
"author": "Travis Fischer <travis@transitivebullsh.it>",
"license": "AGPL-3.0",
"repository": {
"type": "git",
"url": "git+https://github.com/transitive-bullshit/agentic-platform.git",
"directory": "packages/tool-client"
},
"type": "module",
"source": "./src/index.ts",
"types": "./src/index.ts",
"sideEffects": false,
"exports": {
".": "./src/index.ts"
},
"scripts": {
"test": "run-s test:*",
"test:typecheck": "tsc --noEmit",
"test:unit": "vitest run"
},
"dependencies": {
"@agentic/core": "^7.6.7",
"@agentic/platform-api-client": "workspace:*",
"@agentic/platform-core": "workspace:*",
"@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*",
"ky": "catalog:"
}
}

Wyświetl plik

@ -0,0 +1,23 @@
## Project Identifier
`@namespace/project-name`
## Deployment Identifier
`${projectIdentifier}` => `${projectIdentifier}@latest`
`${projectIdentifier}@latest`
`${projectIdentifier}@dev`
`${projectIdentifier}@deploymentHash`
`${projectIdentifier}@version`
## Tool Identifier
`${deploymentIdentifier}/tool_name`
## Tool Names
- Must start with a letter or underscore
- Can include only letters, numbers, and underscores
- Use either camelCase or snake_case consistently across all tools
[OpenAI vs Anthropic vs Google vs MCP tool name restrictions](https://chatgpt.com/share/68419382-73a0-8007-afce-0ded7d9f05e7)

Wyświetl plik

@ -0,0 +1,139 @@
import type { Deployment, Project } from '@agentic/platform-types'
import {
AIFunctionSet,
AIFunctionsProvider,
createAIFunction,
createJsonSchema
} from '@agentic/core'
import { AgenticApiClient } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { parseDeploymentIdentifier } from '@agentic/platform-validators'
import defaultKy, { type KyInstance } from 'ky'
/**
* Agentic tool client which makes it easy to use an Agentic tools product with
* all of the major TypeScript LLM SDKs.
*
* @example
* ```ts
* const client = await AgenticToolClient.fromIdentifier('@agentic/search')
* const result = await client.functions.get('search').execute('search query')
* ```
*/
export class AgenticToolClient extends AIFunctionsProvider {
readonly project: Project
readonly deployment: Deployment
readonly agenticGatewayBaseUrl: string
readonly ky: KyInstance
protected constructor({
project,
deployment,
deploymentIdentifier,
agenticGatewayBaseUrl,
ky
}: {
project: Project
deployment: Deployment
deploymentIdentifier: string
agenticGatewayBaseUrl: string
ky: KyInstance
}) {
super()
this.project = project
this.deployment = deployment
this.agenticGatewayBaseUrl = agenticGatewayBaseUrl
this.ky = ky
this._functions = new AIFunctionSet(
deployment.tools.map((tool) => {
return createAIFunction({
name: tool.name,
description: tool.description ?? '',
inputSchema: createJsonSchema(tool.inputSchema),
execute: async (json) => {
return ky
.post(
`${agenticGatewayBaseUrl}/${deploymentIdentifier}/${tool.name}`,
{
json
}
)
.json()
}
})
})
)
}
override get functions(): AIFunctionSet {
assert(this._functions)
return this._functions
}
/**
* Creates an Agentic tool client from a project or deployment identifier.
*
* You'll generally use a project identifier, which will automatically use
* that project's `latest` deployment, but if you want to target a specific
* version or preview deployment, you can use a fully-qualified deployment
* identifier.
*
* @example
* ```ts
* const client1 = await AgenticToolClient.fromIdentifier('@agentic/search')
* const client2 = await AgenticToolClient.fromIdentifier('@agentic/search@v1.0.0')
* const client3 = await AgenticToolClient.fromIdentifier('@agentic/search@latest')
* ```
*/
static async fromIdentifier(
projectOrDeploymentIdentifier: string,
{
agenticApiClient = new AgenticApiClient(),
agenticGatewayBaseUrl = 'https://gateway.agentic.so',
ky = defaultKy
}: {
agenticApiClient?: AgenticApiClient
agenticGatewayBaseUrl?: string
ky?: KyInstance
} = {}
): Promise<AgenticToolClient> {
const { projectIdentifier, deploymentIdentifier, deploymentVersion } =
parseDeploymentIdentifier(projectOrDeploymentIdentifier, {
strict: false
})
const [project, rawDeployment] = await Promise.all([
agenticApiClient.getPublicProjectByIdentifier({
projectIdentifier,
populate:
deploymentVersion === 'latest' ? ['lastPublishedDeployment'] : []
}),
// Only make 1 API call in the 95% case where the deployment version is
// set to the default value of `latest`.
deploymentVersion === 'latest'
? Promise.resolve(undefined)
: agenticApiClient.getPublicDeploymentByIdentifier({
deploymentIdentifier
})
])
const deployment =
deploymentVersion === 'latest'
? project?.lastPublishedDeployment
: rawDeployment
assert(project, `Project "${projectIdentifier}" not found`)
assert(deployment, `Deployment "${deploymentIdentifier}" not found`)
return new AgenticToolClient({
project,
deployment,
deploymentIdentifier,
agenticGatewayBaseUrl,
ky
})
}
}

Wyświetl plik

@ -0,0 +1 @@
export * from './agentic-tool-client'

Wyświetl plik

@ -0,0 +1,5 @@
{
"extends": "@fisch0920/config/tsconfig-node",
"include": ["src", "*.config.ts"],
"exclude": ["node_modules"]
}

Wyświetl plik

@ -140,6 +140,23 @@ export interface paths {
patch?: never;
trace?: never;
};
"/v1/deployments/public/by-identifier": {
parameters: {
query?: never;
header?: never;
path?: never;
cookie?: never;
};
/** @description Gets a public deployment by its identifier (eg, "@username/project-name@latest"). */
get: operations["getPublicDeploymentByIdentifier"];
put?: never;
post?: never;
delete?: never;
options?: never;
head?: never;
patch?: never;
trace?: never;
};
"/v1/users/{userId}": {
parameters: {
query?: never;
@ -428,7 +445,7 @@ export interface paths {
path?: never;
cookie?: never;
};
/** @description Gets a deployment by its public identifier */
/** @description Gets a deployment by its identifier (eg, "@username/project-name@latest"). */
get: operations["getDeploymentByIdentifier"];
put?: never;
post?: never;
@ -600,46 +617,6 @@ export interface components {
team?: components["schemas"]["Team"];
lastPublishedDeployment?: unknown;
lastDeployment?: unknown;
};
TeamMember: {
createdAt: string;
updatedAt: string;
deletedAt?: string;
userId: string;
teamSlug: string;
teamId: string;
/** @enum {string} */
role: "user" | "admin";
confirmed: boolean;
confirmedAt?: string;
};
/** @description A Consumer represents a user who has subscribed to a Project and is used
* to track usage and billing.
*
* Consumers are linked to a corresponding Stripe Customer and Subscription.
* The Stripe customer will either be the user's default Stripe Customer if the
* project uses the default Agentic platform account, or a customer on the project
* owner's connected Stripe account if the project has Stripe Connect enabled. */
Consumer: {
/** @description Consumer id (e.g. "csmr_tz4a98xxat96iws9zmbrgj3a") */
id: string;
createdAt: string;
updatedAt: string;
deletedAt?: string;
token: string;
plan?: string;
activated: boolean;
source?: string;
/** @description User id (e.g. "user_tz4a98xxat96iws9zmbrgj3a") */
userId: string;
/** @description Project id (e.g. "proj_tz4a98xxat96iws9zmbrgj3a") */
projectId: string;
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
deploymentId: string;
stripeStatus: string;
isStripeSubscriptionActive: boolean;
user?: components["schemas"]["User"];
project?: components["schemas"]["Project"];
deployment?: unknown;
};
/** @description Public deployment identifier (e.g. "@namespace/project-name@{hash|version|latest}") */
@ -899,6 +876,47 @@ export interface components {
defaultRateLimit?: components["schemas"]["RateLimit"] & unknown;
project?: unknown;
};
TeamMember: {
createdAt: string;
updatedAt: string;
deletedAt?: string;
userId: string;
teamSlug: string;
teamId: string;
/** @enum {string} */
role: "user" | "admin";
confirmed: boolean;
confirmedAt?: string;
};
/** @description A Consumer represents a user who has subscribed to a Project and is used
* to track usage and billing.
*
* Consumers are linked to a corresponding Stripe Customer and Subscription.
* The Stripe customer will either be the user's default Stripe Customer if the
* project uses the default Agentic platform account, or a customer on the project
* owner's connected Stripe account if the project has Stripe Connect enabled. */
Consumer: {
/** @description Consumer id (e.g. "csmr_tz4a98xxat96iws9zmbrgj3a") */
id: string;
createdAt: string;
updatedAt: string;
deletedAt?: string;
token: string;
plan?: string;
activated: boolean;
source?: string;
/** @description User id (e.g. "user_tz4a98xxat96iws9zmbrgj3a") */
userId: string;
/** @description Project id (e.g. "proj_tz4a98xxat96iws9zmbrgj3a") */
projectId: string;
/** @description Deployment id (e.g. "depl_tz4a98xxat96iws9zmbrgj3a") */
deploymentId: string;
stripeStatus: string;
isStripeSubscriptionActive: boolean;
user?: components["schemas"]["User"];
project?: components["schemas"]["Project"];
deployment?: unknown;
};
/**
* @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 `originUrl` 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, an MCP server, or a raw HTTP REST API.
*
@ -1269,6 +1287,34 @@ export interface operations {
404: components["responses"]["404"];
};
};
getPublicDeploymentByIdentifier: {
parameters: {
query: {
populate?: ("user" | "team" | "project") | ("user" | "team" | "project")[];
/** @description Public deployment identifier (e.g. "@namespace/project-name@{hash|version|latest}") */
deploymentIdentifier: components["schemas"]["DeploymentIdentifier"];
};
header?: never;
path?: never;
cookie?: never;
};
requestBody?: never;
responses: {
/** @description A deployment object */
200: {
headers: {
[name: string]: unknown;
};
content: {
"application/json": components["schemas"]["Deployment"];
};
};
400: components["responses"]["400"];
401: components["responses"]["401"];
403: components["responses"]["403"];
404: components["responses"]["404"];
};
};
getUser: {
parameters: {
query?: never;

Wyświetl plik

@ -67,3 +67,47 @@ export type AdminConsumer = Simplify<
deployment?: Deployment
}
>
export type AgenticMcpRequestMetadata = {
agenticProxySecret: string
sessionId: string
isCustomerSubscriptionActive: boolean
customerId?: string
customerSubscriptionStatus?: string
customerSubscriptionPlan?: string
userId?: string
userEmail?: string
userUsername?: string
userName?: string
userCreatedAt?: string
userUpdatedAt?: string
deploymentId: string
deploymentIdentifier: string
projectId: string
projectIdentifier: string
ip?: string
} & (
| {
// If the customer has an active subscription, these fields are guaranteed
// to be present in the metadata.
isCustomerSubscriptionActive: true
customerId: string
customerSubscriptionStatus: string
userId: string
userEmail: string
userUsername: string
userCreatedAt: string
userUpdatedAt: string
}
| {
// If the customer does not have an active subscription, then the customer
// fields may or may not be present.
isCustomerSubscriptionActive: false
}
)

Wyświetl plik

@ -1,6 +1,5 @@
export const namespaceBlacklist = new Set([
// restricted because they would be confusing
'agentic',
'admin',
'root',
'sudo',

Plik diff jest za duży Load Diff

Wyświetl plik

@ -1,6 +1,7 @@
packages:
- packages/*
- apps/*
- examples/*
- packages/fixtures/valid/*
catalog:
@ -13,7 +14,7 @@ catalog:
'@fisch0920/config': ^1.1.2
'@fisch0920/drizzle-orm': ^0.43.7
'@fisch0920/drizzle-zod': ^0.7.9
'@hono/node-server': ^1.14.4
'@hono/node-server': 1.14.4
'@hono/sentry': ^1.2.2
'@hono/zod-openapi': ^0.19.8
'@hono/zod-validator': ^0.7.0
@ -52,7 +53,7 @@ catalog:
fastmcp: ^3.4.0
get-port: ^7.1.0
hash-object: ^5.0.1
hono: ^4.8.1
hono: 4.8.1
is-relative-url: ^4.0.0
knip: ^5.61.2
ky: 1.8.1
@ -88,7 +89,7 @@ catalog:
unconfig: ^7.3.2
vite-tsconfig-paths: ^5.1.4
vitest: ^3.2.4
wrangler: ^4.20.3
wrangler: ^4.21.0
zod: ^3.25.67
zod-to-json-schema: ^3.24.5
zod-validation-error: ^3.5.2