feat: WIP kittens

pull/715/head
Travis Fischer 2025-05-31 01:05:30 +07:00
rodzic cc78a67719
commit 3206c522ad
7 zmienionych plików z 128 dodań i 140 usunięć

Wyświetl plik

@ -4,6 +4,7 @@ import { createRoute, type OpenAPIHono } from '@hono/zod-openapi'
import type { AuthenticatedEnv } from '@/lib/types'
import { schema } from '@/db'
import { acl } from '@/lib/acl'
import { aclAdmin } from '@/lib/acl-admin'
import { tryGetDeploymentByIdentifier } from '@/lib/deployments/try-get-deployment-by-identifier'
import {
openapiAuthenticatedSecuritySchemas,
@ -42,6 +43,7 @@ export function registerV1AdminDeploymentsGetDeploymentByIdentifier(
) {
return app.openapi(route, async (c) => {
const { deploymentIdentifier, populate = [] } = c.req.valid('query')
await aclAdmin(c)
const deployment = await tryGetDeploymentByIdentifier(c, {
deploymentIdentifier,

Wyświetl plik

@ -36,6 +36,7 @@
"@hono/zod-validator": "catalog:",
"eventid": "catalog:",
"hono": "catalog:",
"@modelcontextprotocol/sdk": "catalog:",
"type-fest": "catalog:"
},
"devDependencies": {

Wyświetl plik

@ -1,4 +1,4 @@
import type { Deployment } from '@agentic/platform-api-client'
import type { AdminDeployment } from '@agentic/platform-api-client'
import { assert } from '@agentic/platform-core'
import { parseFaasIdentifier } from '@agentic/platform-validators'
@ -7,12 +7,11 @@ import type { Context } from './types'
export async function getDeployment(
ctx: Context,
identifier: string
): Promise<{ deployment: Deployment; toolPath: string }> {
): Promise<{ deployment: AdminDeployment; toolPath: string }> {
const parsedFaas = parseFaasIdentifier(identifier)
assert(parsedFaas, 404, `Invalid deployment identifier "${identifier}"`)
// TODO: maybe use an admin method here to cache it more aggressively?
const deployment = await ctx.client.getDeploymentByIdentifier({
const deployment = await ctx.client.adminGetDeploymentByIdentifier({
deploymentIdentifier: identifier
})
assert(deployment, 404, `Deployment not found "${identifier}"`)

Wyświetl plik

@ -147,7 +147,7 @@ export async function resolveOriginRequest(ctx: Context) {
}
}
// enforce requests rate limit
// enforce requests rate limits
if (rateLimit) {
await enforceRateLimit(ctx, {
id: consumer ? consumer.id : ip,
@ -158,10 +158,11 @@ export async function resolveOriginRequest(ctx: Context) {
})
}
// TODO: decide whether or not this is something we actually want to support
// for long-term DX
const targetUrlOverride = isProd ? null : req.headers.get('x-saasify-target')
const baseUrl = (targetUrlOverride || deployment._url).replaceAll(/\/$/g, '')
const baseUrl = deployment.originUrl.replaceAll(/\/$/g, '')
// TODO: Everything from here on depends on the origin adapter type.
// For MCP, we need(?) to use an McpClient and SSEClientTransport?
// For OpenAPI and raw, we need to make an origin HTTP request.
const originUrl = `${baseUrl}${toolPath}${search}`
console.log('originUrl', originUrl)

Wyświetl plik

@ -8,13 +8,16 @@ import defaultKy, { type KyInstance } from 'ky'
import type { operations } from './openapi'
import type {
AdminDeployment,
AuthorizeResult,
AuthTokens,
AuthUser,
Consumer,
Deployment,
OnUpdateAuthSessionFunction,
Project,
Team,
TeamMember,
User
} from './types'
import { subjects } from './subjects'
@ -172,7 +175,7 @@ export class AgenticApiClient {
)
}
async getMe(): Promise<OperationResponse<'getUser'>> {
async getMe(): Promise<User> {
const user = await this.verifyAuthAndRefreshIfNecessary()
return this.ky.get(`v1/users/${user.id}`).json()
@ -181,14 +184,14 @@ export class AgenticApiClient {
async getUser({
userId,
...searchParams
}: OperationParameters<'getUser'>): Promise<OperationResponse<'getUser'>> {
}: OperationParameters<'getUser'>): Promise<User> {
return this.ky.get(`v1/users/${userId}`, { searchParams }).json()
}
async updateUser(
user: OperationBody<'updateUser'>,
{ userId, ...searchParams }: OperationParameters<'updateUser'>
): Promise<OperationResponse<'updateUser'>> {
): Promise<User> {
return this.ky
.post(`v1/users/${userId}`, { json: user, searchParams })
.json()
@ -196,28 +199,28 @@ export class AgenticApiClient {
async listTeams(
searchParams: OperationParameters<'listTeams'>
): Promise<OperationResponse<'listTeams'>> {
): Promise<Array<Team>> {
return this.ky.get('v1/teams', { searchParams }).json()
}
async createTeam(
team: OperationBody<'createTeam'>,
searchParams: OperationParameters<'createTeam'> = {}
): Promise<OperationResponse<'createTeam'>> {
): Promise<Team> {
return this.ky.post('v1/teams', { json: team, searchParams }).json()
}
async getTeam({
teamId,
...searchParams
}: OperationParameters<'getTeam'>): Promise<OperationResponse<'getTeam'>> {
}: OperationParameters<'getTeam'>): Promise<Team> {
return this.ky.get(`v1/teams/${teamId}`, { searchParams }).json()
}
async updateTeam(
team: OperationBody<'updateTeam'>,
{ teamId, ...searchParams }: OperationParameters<'updateTeam'>
): Promise<OperationResponse<'updateTeam'>> {
): Promise<Team> {
return this.ky
.post(`v1/teams/${teamId}`, { json: team, searchParams })
.json()
@ -226,16 +229,14 @@ export class AgenticApiClient {
async deleteTeam({
teamId,
...searchParams
}: OperationParameters<'deleteTeam'>): Promise<
OperationResponse<'deleteTeam'>
> {
}: OperationParameters<'deleteTeam'>): Promise<Team> {
return this.ky.delete(`v1/teams/${teamId}`, { searchParams }).json()
}
async createTeamMember(
member: OperationBody<'createTeamMember'>,
{ teamId, ...searchParams }: OperationParameters<'createTeamMember'>
): Promise<OperationResponse<'createTeamMember'>> {
): Promise<TeamMember> {
return this.ky
.post(`v1/teams/${teamId}/members`, { json: member, searchParams })
.json()
@ -244,7 +245,7 @@ export class AgenticApiClient {
async updateTeamMember(
member: OperationBody<'updateTeamMember'>,
{ teamId, userId, ...searchParams }: OperationParameters<'updateTeamMember'>
): Promise<OperationResponse<'updateTeamMember'>> {
): Promise<TeamMember> {
return this.ky
.post(`v1/teams/${teamId}/members/${userId}`, {
json: member,
@ -257,9 +258,7 @@ export class AgenticApiClient {
teamId,
userId,
...searchParams
}: OperationParameters<'deleteTeamMember'>): Promise<
OperationResponse<'deleteTeamMember'>
> {
}: OperationParameters<'deleteTeamMember'>): Promise<TeamMember> {
return this.ky
.delete(`v1/teams/${teamId}/members/${userId}`, { searchParams })
.json()
@ -268,14 +267,12 @@ export class AgenticApiClient {
async listProjects<
TPopulate extends NonNullable<
OperationParameters<'listProjects'>['populate']
>[number] = never
>[number]
>(
searchParams: OperationParameters<'listProjects'> & {
populate?: TPopulate[]
}
): Promise<
Simplify<OperationResponse<'listProjects'> & PopulateProject<TPopulate>>
> {
): Promise<Array<PopulateProject<TPopulate>>> {
return this.ky
.get('v1/projects', { searchParams: sanitizeSearchParams(searchParams) })
.json()
@ -284,22 +281,20 @@ export class AgenticApiClient {
async createProject(
project: OperationBody<'createProject'>,
searchParams: OperationParameters<'createProject'> = {}
): Promise<OperationResponse<'createProject'>> {
): Promise<Project> {
return this.ky.post('v1/projects', { json: project, searchParams }).json()
}
async getProject<
TPopulate extends NonNullable<
OperationParameters<'getProject'>['populate']
>[number] = never
>[number]
>({
projectId,
...searchParams
}: OperationParameters<'getProject'> & {
populate?: TPopulate[]
}): Promise<
Simplify<OperationResponse<'getProject'> & PopulateProject<TPopulate>>
> {
}): Promise<PopulateProject<TPopulate>> {
return this.ky
.get(`v1/projects/${projectId}`, {
searchParams: sanitizeSearchParams(searchParams)
@ -310,16 +305,12 @@ export class AgenticApiClient {
async getProjectByIdentifier<
TPopulate extends NonNullable<
OperationParameters<'getProjectByIdentifier'>['populate']
>[number] = never
>[number]
>(
searchParams: OperationParameters<'getProjectByIdentifier'> & {
populate?: TPopulate[]
}
): Promise<
Simplify<
OperationResponse<'getProjectByIdentifier'> & PopulateProject<TPopulate>
>
> {
): Promise<PopulateProject<TPopulate>> {
return this.ky
.get(`v1/projects/by-identifier`, {
searchParams: sanitizeSearchParams(searchParams)
@ -330,7 +321,7 @@ export class AgenticApiClient {
async updateProject(
project: OperationBody<'updateProject'>,
{ projectId, ...searchParams }: OperationParameters<'updateProject'>
): Promise<OperationResponse<'updateProject'>> {
): Promise<Project> {
return this.ky
.post(`v1/projects/${projectId}`, { json: project, searchParams })
.json()
@ -339,15 +330,13 @@ export class AgenticApiClient {
async getConsumer<
TPopulate extends NonNullable<
OperationParameters<'getConsumer'>['populate']
>[number] = never
>[number]
>({
consumerId,
...searchParams
}: OperationParameters<'getConsumer'> & {
populate?: TPopulate[]
}): Promise<
Simplify<OperationResponse<'getConsumer'> & PopulateConsumer<TPopulate>>
> {
}): Promise<PopulateConsumer<TPopulate>> {
return this.ky
.get(`v1/consumers/${consumerId}`, {
searchParams: sanitizeSearchParams(searchParams)
@ -358,7 +347,7 @@ export class AgenticApiClient {
async updateConsumer(
consumer: OperationBody<'updateConsumer'>,
{ consumerId, ...searchParams }: OperationParameters<'updateConsumer'>
): Promise<OperationResponse<'updateConsumer'>> {
): Promise<Consumer> {
return this.ky
.post(`v1/consumers/${consumerId}`, { json: consumer, searchParams })
.json()
@ -367,16 +356,14 @@ export class AgenticApiClient {
async createConsumer(
consumer: OperationBody<'createConsumer'>,
searchParams: OperationParameters<'createConsumer'> = {}
): Promise<OperationResponse<'createConsumer'>> {
): Promise<Consumer> {
return this.ky.post('v1/consumers', { json: consumer, searchParams }).json()
}
async refreshConsumerToken({
consumerId,
...searchParams
}: OperationParameters<'refreshConsumerToken'>): Promise<
OperationResponse<'refreshConsumerToken'>
> {
}: OperationParameters<'refreshConsumerToken'>): Promise<Consumer> {
return this.ky
.post(`v1/consumers/${consumerId}/refresh-token`, { searchParams })
.json()
@ -385,15 +372,13 @@ export class AgenticApiClient {
async listConsumers<
TPopulate extends NonNullable<
OperationParameters<'listConsumers'>['populate']
>[number] = never
>[number]
>({
projectId,
...searchParams
}: OperationParameters<'listConsumers'> & {
populate?: TPopulate[]
}): Promise<
Simplify<OperationResponse<'listConsumers'> & PopulateConsumer<TPopulate>>
> {
}): Promise<Array<PopulateConsumer<TPopulate>>> {
return this.ky
.get(`v1/projects/${projectId}/consumers`, {
searchParams: sanitizeSearchParams(searchParams)
@ -404,15 +389,13 @@ export class AgenticApiClient {
async getDeployment<
TPopulate extends NonNullable<
OperationParameters<'getDeployment'>['populate']
>[number] = never
>[number]
>({
deploymentId,
...searchParams
}: OperationParameters<'getDeployment'> & {
populate?: TPopulate[]
}): Promise<
Simplify<OperationResponse<'getDeployment'> & PopulateDeployment<TPopulate>>
> {
}): Promise<PopulateDeployment<TPopulate>> {
return this.ky
.get(`v1/deployments/${deploymentId}`, {
searchParams: sanitizeSearchParams(searchParams)
@ -423,17 +406,12 @@ export class AgenticApiClient {
async getDeploymentByIdentifier<
TPopulate extends NonNullable<
OperationParameters<'getDeploymentByIdentifier'>['populate']
>[number] = never
>[number]
>(
searchParams: OperationParameters<'getDeploymentByIdentifier'> & {
populate?: TPopulate[]
}
): Promise<
Simplify<
OperationResponse<'getDeploymentByIdentifier'> &
PopulateDeployment<TPopulate>
>
> {
): Promise<PopulateDeployment<TPopulate>> {
return this.ky
.get(`v1/deployments/by-identifier`, {
searchParams: sanitizeSearchParams(searchParams)
@ -444,7 +422,7 @@ export class AgenticApiClient {
async updateDeployment(
deployment: OperationBody<'updateDeployment'>,
{ deploymentId, ...searchParams }: OperationParameters<'updateDeployment'>
): Promise<OperationResponse<'updateDeployment'>> {
): Promise<Deployment> {
return this.ky
.post(`v1/deployments/${deploymentId}`, {
json: deployment,
@ -456,16 +434,12 @@ export class AgenticApiClient {
async listDeployments<
TPopulate extends NonNullable<
OperationParameters<'listDeployments'>['populate']
>[number] = never
>[number]
>(
searchParams: OperationParameters<'listDeployments'> & {
populate?: TPopulate[]
}
): Promise<
Simplify<
OperationResponse<'listDeployments'> & PopulateDeployment<TPopulate>
>
> {
): Promise<Array<PopulateDeployment<TPopulate>>> {
return this.ky
.get('v1/deployments', {
searchParams: sanitizeSearchParams(searchParams)
@ -476,7 +450,7 @@ export class AgenticApiClient {
async createDeployment(
deployment: OperationBody<'createDeployment'>,
searchParams: OperationParameters<'createDeployment'> = {}
): Promise<OperationResponse<'createDeployment'>> {
): Promise<Deployment> {
return this.ky
.post('v1/deployments', { json: deployment, searchParams })
.json()
@ -485,7 +459,7 @@ export class AgenticApiClient {
async publishDeployment(
deployment: OperationBody<'publishDeployment'>,
{ deploymentId, ...searchParams }: OperationParameters<'publishDeployment'>
): Promise<OperationResponse<'publishDeployment'>> {
): Promise<Deployment> {
return this.ky
.post(`v1/deployments/${deploymentId}/publish`, {
json: deployment,
@ -497,17 +471,13 @@ export class AgenticApiClient {
async adminGetConsumerByToken<
TPopulate extends NonNullable<
OperationParameters<'adminGetConsumerByToken'>['populate']
>[number] = never
>[number]
>({
token,
...searchParams
}: OperationParameters<'adminGetConsumerByToken'> & {
populate?: TPopulate[]
}): Promise<
Simplify<
OperationResponse<'adminGetConsumerByToken'> & PopulateConsumer<TPopulate>
>
> {
}): Promise<PopulateConsumer<TPopulate>> {
return this.ky
.get(`v1/admin/consumers/tokens/${token}`, {
searchParams: sanitizeSearchParams(searchParams)
@ -518,17 +488,12 @@ export class AgenticApiClient {
async adminGetDeploymentByIdentifier<
TPopulate extends NonNullable<
OperationParameters<'adminGetDeploymentByIdentifier'>['populate']
>[number] = never
>[number]
>(
searchParams: OperationParameters<'adminGetDeploymentByIdentifier'> & {
populate?: TPopulate[]
}
): Promise<
Simplify<
OperationResponse<'adminGetDeploymentByIdentifier'> &
PopulateDeployment<TPopulate>
>
> {
): Promise<PopulateDeployment<TPopulate, AdminDeployment>> {
return this.ky
.get(`v1/admin/deployments/by-identifier`, {
searchParams: sanitizeSearchParams(searchParams)
@ -545,8 +510,10 @@ type OperationParameters<
([Q] extends [never] ? unknown : Q) & ([P] extends [never] ? unknown : P)
>
type OperationResponse<T extends keyof operations> =
operations[T]['responses'][200]['content']['application/json']
// Currently unused because some types need customization (e.g. Deployment) over
// the default OpenAPI types.
// type OperationResponse<T extends keyof operations> =
// operations[T]['responses'][200]['content']['application/json']
type OperationKeysWithRequestBody = {
[K in keyof operations]: operations[K]['requestBody'] extends {
@ -565,55 +532,67 @@ type OperationBody<
| undefined = operations[T]['requestBody']['content']['application/json']
> = B
type PopulateProject<TPopulate> = (TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'team'
? {
team: Team
}
: unknown) &
(TPopulate extends 'lastPublishedDeployment'
? {
lastPublishedDeployment?: Deployment
}
: unknown) &
(TPopulate extends 'lastDeployment'
? {
lastDeployment?: Deployment
}
: unknown)
type PopulateProject<TPopulate> = Simplify<
Project &
(TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'team'
? {
team: Team
}
: unknown) &
(TPopulate extends 'lastPublishedDeployment'
? {
lastPublishedDeployment?: Deployment
}
: unknown) &
(TPopulate extends 'lastDeployment'
? {
lastDeployment?: Deployment
}
: unknown)
>
type PopulateDeployment<TPopulate> = (TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'team'
? {
team: Team
}
: unknown) &
(TPopulate extends 'project'
? {
project: Project
}
: unknown)
type PopulateDeployment<
TPopulate,
TDeployment extends Deployment = Deployment
> = Simplify<
TDeployment &
(TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'team'
? {
team: Team
}
: unknown) &
(TPopulate extends 'project'
? {
project: Project
}
: unknown)
>
type PopulateConsumer<TPopulate> = (TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'project'
? {
project: Project
}
: unknown) &
(TPopulate extends 'deployment'
? {
deployment: Deployment
}
: unknown)
type PopulateConsumer<TPopulate> = Simplify<
Consumer &
(TPopulate extends 'user'
? {
user: User
}
: unknown) &
(TPopulate extends 'project'
? {
project: Project
}
: unknown) &
(TPopulate extends 'deployment'
? {
deployment: Deployment
}
: unknown)
>

Wyświetl plik

@ -330,7 +330,7 @@ export interface paths {
path?: never;
cookie?: never;
};
/** @description Gets a consumer by API token */
/** @description Gets a consumer by API token (admin-only) */
get: operations["adminGetConsumerByToken"];
put?: never;
post?: never;
@ -1674,7 +1674,7 @@ export interface operations {
};
requestBody?: never;
responses: {
/** @description A consumer object */
/** @description An admin consumer object */
200: {
headers: {
[name: string]: unknown;

Wyświetl plik

@ -16,6 +16,12 @@ export type Deployment = Simplify<
}
>
export type AdminDeployment = Simplify<
Omit<components['schemas']['AdminDeployment'], 'pricingPlans'> & {
pricingPlans: PricingPlan[]
}
>
export type ProjectIdentifier = components['schemas']['ProjectIdentifier']
export type DeploymentIdentifier = components['schemas']['DeploymentIdentifier']