kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: WIP kittens
rodzic
67f564bff8
commit
b0d3dcf589
|
@ -34,9 +34,9 @@
|
||||||
"@agentic/platform-types": "workspace:*",
|
"@agentic/platform-types": "workspace:*",
|
||||||
"@agentic/platform-validators": "workspace:*",
|
"@agentic/platform-validators": "workspace:*",
|
||||||
"@hono/zod-validator": "catalog:",
|
"@hono/zod-validator": "catalog:",
|
||||||
|
"@modelcontextprotocol/sdk": "catalog:",
|
||||||
"eventid": "catalog:",
|
"eventid": "catalog:",
|
||||||
"hono": "catalog:",
|
"hono": "catalog:",
|
||||||
"@modelcontextprotocol/sdk": "catalog:",
|
|
||||||
"type-fest": "catalog:"
|
"type-fest": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
|
|
@ -11,13 +11,15 @@ export async function enforceRateLimit(
|
||||||
method,
|
method,
|
||||||
pathname
|
pathname
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id?: string
|
||||||
interval: number
|
interval: number
|
||||||
maxPerInterval: number
|
maxPerInterval: number
|
||||||
method: string
|
method: string
|
||||||
pathname: string
|
pathname: string
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
|
assert(id, 400, 'Unauthenticated requests must have a valid IP address')
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
assert(ctx, 500, 'not implemented')
|
assert(ctx, 500, 'not implemented')
|
||||||
assert(id, 500, 'not implemented')
|
assert(id, 500, 'not implemented')
|
||||||
|
|
|
@ -2,7 +2,7 @@ import { assert } from '@agentic/platform-core'
|
||||||
|
|
||||||
import type { AdminConsumer, Context } from './types'
|
import type { AdminConsumer, Context } from './types'
|
||||||
|
|
||||||
export async function getConsumer(
|
export async function getAdminConsumer(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
token: string
|
token: string
|
||||||
): Promise<AdminConsumer> {
|
): Promise<AdminConsumer> {
|
|
@ -4,7 +4,7 @@ import { parseFaasIdentifier } from '@agentic/platform-validators'
|
||||||
|
|
||||||
import type { Context } from './types'
|
import type { Context } from './types'
|
||||||
|
|
||||||
export async function getDeployment(
|
export async function getAdminDeployment(
|
||||||
ctx: Context,
|
ctx: Context,
|
||||||
identifier: string
|
identifier: string
|
||||||
): Promise<{ deployment: AdminDeployment; toolPath: string }> {
|
): Promise<{ deployment: AdminDeployment; toolPath: string }> {
|
|
@ -2,8 +2,9 @@ import type { PricingPlan, RateLimit } from '@agentic/platform-types'
|
||||||
import { assert } from '@agentic/platform-core'
|
import { assert } from '@agentic/platform-core'
|
||||||
|
|
||||||
import type { AdminConsumer, Context, ResolvedOriginRequest } from './types'
|
import type { AdminConsumer, Context, ResolvedOriginRequest } from './types'
|
||||||
import { getConsumer } from './get-consumer'
|
import { enforceRateLimit } from './enforce-rate-limit'
|
||||||
import { getDeployment } from './get-deployment'
|
import { getAdminConsumer } from './get-admin-consumer'
|
||||||
|
import { getAdminDeployment } from './get-admin-deployment'
|
||||||
import { getTool } from './get-tool'
|
import { getTool } from './get-tool'
|
||||||
import { updateOriginRequest } from './update-origin-request'
|
import { updateOriginRequest } from './update-origin-request'
|
||||||
|
|
||||||
|
@ -23,8 +24,13 @@ export async function resolveOriginRequest(
|
||||||
|
|
||||||
const { search, pathname } = requestUrl
|
const { search, pathname } = requestUrl
|
||||||
const method = req.method.toLowerCase()
|
const method = req.method.toLowerCase()
|
||||||
|
const requestPathParts = pathname.split('/')
|
||||||
|
const requestPath =
|
||||||
|
requestPathParts[0] === 'mcp'
|
||||||
|
? requestPathParts.slice(1).join('/')
|
||||||
|
: pathname
|
||||||
|
|
||||||
const { deployment, toolPath } = await getDeployment(ctx, pathname)
|
const { deployment, toolPath } = await getAdminDeployment(ctx, requestPath)
|
||||||
|
|
||||||
const tool = getTool({
|
const tool = getTool({
|
||||||
method,
|
method,
|
||||||
|
@ -50,7 +56,7 @@ export async function resolveOriginRequest(
|
||||||
.trim()
|
.trim()
|
||||||
|
|
||||||
if (token) {
|
if (token) {
|
||||||
consumer = await getConsumer(ctx, token)
|
consumer = await getAdminConsumer(ctx, token)
|
||||||
assert(consumer, 401, `Invalid auth token "${token}"`)
|
assert(consumer, 401, `Invalid auth token "${token}"`)
|
||||||
assert(
|
assert(
|
||||||
consumer.isStripeSubscriptionActive,
|
consumer.isStripeSubscriptionActive,
|
||||||
|
@ -155,12 +161,11 @@ export async function resolveOriginRequest(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// enforce requests rate limits
|
|
||||||
if (rateLimit) {
|
if (rateLimit) {
|
||||||
await enforceRateLimit(ctx, {
|
await enforceRateLimit(ctx, {
|
||||||
id: consumer ? consumer.id : ip,
|
id: consumer?.id ?? ip,
|
||||||
duration: rateLimit.interval * 1000,
|
interval: rateLimit.interval * 1000,
|
||||||
max: rateLimit.maxPerInterval,
|
maxPerInterval: rateLimit.maxPerInterval,
|
||||||
method,
|
method,
|
||||||
pathname
|
pathname
|
||||||
})
|
})
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
|
||||||
|
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
|
||||||
|
|
||||||
|
import type { Context, ResolvedOriginRequest } from './lib/types'
|
||||||
|
|
||||||
|
// TODO: https://github.com/modelcontextprotocol/servers/blob/8fb7bbdab73eddb42aba72e8eab81102efe1d544/src/everything/sse.ts
|
||||||
|
// TODO: https://github.com/cloudflare/agents
|
||||||
|
|
||||||
|
const transports: Map<string, SSEServerTransport> = new Map<
|
||||||
|
string,
|
||||||
|
SSEServerTransport
|
||||||
|
>()
|
||||||
|
|
||||||
|
export async function handleMCPRequest(
|
||||||
|
ctx: Context,
|
||||||
|
resolvedOriginRequest: ResolvedOriginRequest
|
||||||
|
) {
|
||||||
|
const serverTransport = new SSEServerTransport()
|
||||||
|
const server = new McpServer({
|
||||||
|
name: 'weather',
|
||||||
|
version: '1.0.0',
|
||||||
|
capabilities: {
|
||||||
|
resources: {},
|
||||||
|
tools: {}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -62,6 +62,17 @@ export default {
|
||||||
const resolvedOriginRequest = await resolveOriginRequest(ctx)
|
const resolvedOriginRequest = await resolveOriginRequest(ctx)
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
switch (resolvedOriginRequest.deployment.originAdapter.type) {
|
||||||
|
case 'openapi':
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'raw':
|
||||||
|
break
|
||||||
|
|
||||||
|
case 'mcp':
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
const originReqCacheKey = await getOriginRequestCacheKey(originReq)
|
const originReqCacheKey = await getOriginRequestCacheKey(originReq)
|
||||||
originStartTime = Date.now()
|
originStartTime = Date.now()
|
||||||
|
|
||||||
|
|
24
readme.md
24
readme.md
|
@ -7,8 +7,6 @@
|
||||||
|
|
||||||
## TODO
|
## TODO
|
||||||
|
|
||||||
- **api gateway**
|
|
||||||
- signed requests
|
|
||||||
- **webapp**
|
- **webapp**
|
||||||
- end-to-end working examples
|
- end-to-end working examples
|
||||||
- raw
|
- raw
|
||||||
|
@ -25,7 +23,7 @@
|
||||||
- (openauth password emails and `sendCode`)
|
- (openauth password emails and `sendCode`)
|
||||||
- stripe-related billing emails
|
- stripe-related billing emails
|
||||||
- auth
|
- auth
|
||||||
- custom auth pages
|
- custom auth pages for `openauth`
|
||||||
- re-add support for teams / organizations
|
- re-add support for teams / organizations
|
||||||
- consider switching to [consola](https://github.com/unjs/consola) for logging?
|
- consider switching to [consola](https://github.com/unjs/consola) for logging?
|
||||||
- consider switching to `bun` (for `--hot` reloading!!)
|
- consider switching to `bun` (for `--hot` reloading!!)
|
||||||
|
@ -36,11 +34,23 @@
|
||||||
- validate stability of pricing plan slugs across deployments
|
- validate stability of pricing plan slugs across deployments
|
||||||
- same for pricing plan line-items
|
- same for pricing plan line-items
|
||||||
- replace `ms` package
|
- replace `ms` package
|
||||||
- API gateway MCP server vs OpenAPI API gateway
|
|
||||||
- share hono middleware and utils across apps/api and apps/gateway
|
|
||||||
- or combine these together? ehhhh
|
|
||||||
- add username / team name blacklist
|
- add username / team name blacklist
|
||||||
- admin, internal, mcp, etc
|
- admin, internal, mcp, sse, etc
|
||||||
|
- **API gateway**
|
||||||
|
- share hono middleware and utils across apps/api and apps/gateway
|
||||||
|
- or combine these together? ehhhh
|
||||||
|
- MCP server vs REST gateway on public and internal sides
|
||||||
|
- RAW: `METHOD gateway.agentic.so/deploymentIdentifier/<pathname>`
|
||||||
|
- => Raw HTTP: `METHOD originUrl/<pathname>` simple HTTP proxy request
|
||||||
|
- REST: `POST gateway.agentic.so/deploymentIdentifier/toolName`
|
||||||
|
- => MCP: `MCPClient.callTool` with JSON body parameters
|
||||||
|
- => OpenAPI: `GET/POST/ETC originUrl/toolName` operation with transformed JSON body params
|
||||||
|
- MCP: `mcp.agentic.so/deploymentIdentifier/sse` MCP server
|
||||||
|
- => MCP: `MCPClient.callTool` just proxying tool call
|
||||||
|
- => OpenAPI: `GET/POST/ETC originUrl/toolName` operation with transformed tool params
|
||||||
|
- add support for caching
|
||||||
|
- add support for custom headers on response
|
||||||
|
- signed requests
|
||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
|
|
Ładowanie…
Reference in New Issue