pull/715/head
Travis Fischer 2025-06-01 01:28:19 +07:00
rodzic 2e458b226f
commit 449bb4a926
8 zmienionych plików z 161 dodań i 138 usunięć

Wyświetl plik

@ -0,0 +1,103 @@
import type {
AdminDeployment,
OpenAPIToolOperation
} from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
export async function createRequestForOpenAPIOperation({
request,
operation,
deployment
}: {
request: Request
operation: OpenAPIToolOperation
deployment: AdminDeployment
}): Promise<Request> {
const tempInitialRequest = request.clone()
const tempInitialRequestBody: any = await tempInitialRequest.json()
const params = Object.entries(operation.parameterSources)
// TODO: Make this more efficient by changing the `parameterSources` data structure
const bodyParams = params.filter(([_key, source]) => source === 'body')
const formDataParams = params.filter(
([_key, source]) => source === 'formData'
)
const headerParams = params.filter(([_key, source]) => source === 'header')
const pathParams = params.filter(([_key, source]) => source === 'path')
const queryParams = params.filter(([_key, source]) => source === 'query')
const cookieParams = params.filter(([_key, source]) => source === 'cookie')
assert(
!cookieParams.length,
500,
'Cookie parameters for OpenAPI operations are not yet supported. If you need cookie parameter support, please contact support@agentic.so.'
)
const headers: Record<string, string> = {}
if (headerParams.length > 0) {
for (const [key] of headerParams) {
headers[key] = tempInitialRequest.headers.get(key) as string
}
}
for (const [key] of cookieParams) {
headers[key] = tempInitialRequestBody[key] as string
}
let body: string | undefined
if (bodyParams.length > 0) {
body = JSON.stringify(
Object.fromEntries(
bodyParams.map(([key]) => [key, tempInitialRequestBody[key] as any])
)
)
headers['content-type'] ??= 'application/json'
} else if (formDataParams.length > 0) {
body = JSON.stringify(
Object.fromEntries(
formDataParams.map(([key]) => [key, tempInitialRequestBody[key] as any])
)
)
headers['content-type'] ??= 'application/x-www-form-urlencoded'
}
let path = operation.path
if (pathParams.length > 0) {
for (const [key] of pathParams) {
const value: string = tempInitialRequestBody[key]
assert(value, 400, `Missing required parameter "${key}"`)
const pathParamPlaceholder = `{${key}}`
assert(
path.includes(pathParamPlaceholder),
500,
`Misconfigured OpenAPI deployment "${deployment.id}": invalid path "${operation.path}" missing required path parameter "${key}"`
)
path = path.replaceAll(pathParamPlaceholder, value)
}
}
assert(
!/\{\w+\}/.test(path),
500,
`Misconfigured OpenAPI deployment "${deployment.id}": invalid path "${operation.path}"`
)
const query = new URLSearchParams()
for (const [key] of queryParams) {
query.set(key, tempInitialRequestBody[key] as string)
}
const queryString = query.toString()
const originRequestUrl = `${deployment.originUrl}${path}${
queryString ? `?${queryString}` : ''
}`
return new Request(originRequestUrl, {
method: operation.method,
body,
headers
})
}

Wyświetl plik

@ -2,6 +2,7 @@ import type { PricingPlan, RateLimit } from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
import type { AdminConsumer, Context, ResolvedOriginRequest } from './types'
import { createRequestForOpenAPIOperation } from './create-request-for-openapi-operation'
import { enforceRateLimit } from './enforce-rate-limit'
import { getAdminConsumer } from './get-admin-consumer'
import { getAdminDeployment } from './get-admin-deployment'
@ -84,18 +85,6 @@ export async function resolveOriginRequest(
} else {
// For unauthenticated requests, default to a free pricing plan if available.
pricingPlan = deployment.pricingPlans.find((plan) => plan.slug === 'free')
// assert(
// pricingPlan,
// 403,
// `Auth error, unable to find matching pricing plan for project "${deployment.project}"`
// )
// assert(
// !pricingPlan.auth,
// 403,
// `Auth error, encountered invalid pricing plan "${pricingPlan.slug}" for project "${deployment.project}"`
// )
}
let rateLimit: RateLimit | undefined | null
@ -175,97 +164,14 @@ export async function resolveOriginRequest(
let originRequest: Request | undefined
if (originAdapter.type === 'openapi' || originAdapter.type === 'raw') {
// TODO: For OpenAPI, we need to convert from POST to the correct operation?
// Or, do we only support a single public MCP interface?
if (originAdapter.type === 'openapi') {
const operation = originAdapter.toolToOperationMap[tool.name]
assert(operation, 404, `Tool "${tool.name}" not found in OpenAPI spec`)
const tempInitialRequest = ctx.req.clone()
const tempInitialRequestBody: any = await tempInitialRequest.json()
const params = Object.entries(operation.parameterSources)
const bodyParams = params.filter(([_key, source]) => source === 'body')
const formDataParams = params.filter(
([_key, source]) => source === 'formData'
)
const headerParams = params.filter(
([_key, source]) => source === 'header'
)
const pathParams = params.filter(([_key, source]) => source === 'path')
const queryParams = params.filter(([_key, source]) => source === 'query')
const cookieParams = params.filter(
([_key, source]) => source === 'cookie'
)
const headers: Record<string, string> = {}
if (headerParams.length > 0) {
for (const [key] of headerParams) {
headers[key] = tempInitialRequest.headers.get(key) as string
}
}
let body: string | undefined
if (bodyParams.length > 0) {
body = JSON.stringify(
Object.fromEntries(
bodyParams.map(([key]) => [key, tempInitialRequestBody[key] as any])
)
)
headers['content-type'] ??= 'application/json'
} else if (formDataParams.length > 0) {
body = JSON.stringify(
Object.fromEntries(
formDataParams.map(([key]) => [
key,
tempInitialRequestBody[key] as any
])
)
)
headers['content-type'] ??= 'application/x-www-form-urlencoded'
}
let path = operation.path
if (pathParams.length > 0) {
for (const [key] of pathParams) {
const value: string = tempInitialRequestBody[key]
assert(value, 400, `Missing required parameter "${key}"`)
const pathParamPlaceholder = `{${key}}`
assert(
path.includes(pathParamPlaceholder),
500,
`Misconfigured OpenAPI deployment "${deployment.id}": invalid path "${operation.path}" missing required path parameter "${key}"`
)
path = path.replaceAll(pathParamPlaceholder, value)
}
}
assert(
!/\{\w+\}/.test(path),
500,
`Misconfigured OpenAPI deployment "${deployment.id}": invalid path "${operation.path}"`
)
const query = new URLSearchParams()
for (const [key] of queryParams) {
query.set(key, tempInitialRequestBody[key] as string)
}
for (const [key] of cookieParams) {
headers[key] = tempInitialRequestBody[key] as string
}
const queryString = query.toString()
const originRequestUrl = `${deployment.originUrl}${path}${
queryString ? `?${queryString}` : ''
}`
originRequest = new Request(originRequestUrl, {
method: operation.method,
body,
headers
originRequest = await createRequestForOpenAPIOperation({
request: req,
operation,
deployment
})
} else {
const originRequestUrl = `${deployment.originUrl}${toolPath}${search}`

Wyświetl plik

@ -1,27 +1,27 @@
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js'
import { SSEServerTransport } from '@modelcontextprotocol/sdk/server/sse.js'
// 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
>()
// const transports: Map<string, SSEServerTransport> = new Map<
// string,
// SSEServerTransport
// >()
export async function handleMCPRequest(
ctx: Context,
resolvedOriginRequest: ResolvedOriginRequest
_ctx: Context,
_resolvedOriginRequest: ResolvedOriginRequest
) {
const serverTransport = new SSEServerTransport()
const server = new McpServer({
name: 'weather',
version: '1.0.0',
capabilities: {
resources: {},
tools: {}
}
})
// const serverTransport = new SSEServerTransport()
// const server = new McpServer({
// name: 'weather',
// version: '1.0.0',
// capabilities: {
// resources: {},
// tools: {}
// }
// })
}

Wyświetl plik

@ -4,7 +4,7 @@
declare namespace Cloudflare {
interface Env {
ENVIRONMENT: 'development' | 'production'
AGENTIC_API_BASE_URL: 'http://localhost:3000' | 'https://api.agentic.ai'
AGENTIC_API_BASE_URL: 'http://localhost:3000' | 'https://api.agentic.so'
DO_RATE_LIMITER: DurableObjectNamespace<
import('./worker').DurableObjectRateLimiter
>

Wyświetl plik

@ -63,18 +63,22 @@ export default {
try {
originStartTime = Date.now()
let originResponse: Response | undefined
switch (resolvedOriginRequest.deployment.originAdapter.type) {
case 'openapi':
return fetch(resolvedOriginRequest.originRequest!)
originResponse = await fetch(resolvedOriginRequest.originRequest!)
break
case 'raw':
return fetch(resolvedOriginRequest.originRequest!)
originResponse = await fetch(resolvedOriginRequest.originRequest!)
break
case 'mcp':
break
throw new Error('MCP not yet supported')
}
const res = new Response(originResponse.body, originResponse)
recordTimespans()
// Record the time it took for both the origin and gateway proxy to respond
@ -101,28 +105,28 @@ export default {
type: err.type,
code: err.code
}),
{ status: 500, headers: globalResHeaders }
{ status: 500 }
)
return res
} finally {
const now = Date.now()
// const now = Date.now()
// Report usage.
// Note that we are not awaiting the results of this on purpose so we can
// return the response to the client immediately.
ctx.waitUntil(
reportUsage(ctx, {
...call,
cache: res!.headers.get('cf-cache-status'),
status: res!.status,
timestamp: Math.ceil(now / 1000),
computeTime: originTimespan!,
gatewayTime: gatewayTimespan!,
// TODO: record correct bandwidth of request + response content-length
bandwidth: 0
})
)
// TODO
// ctx.waitUntil(
// reportUsage(ctx, {
// ...call,
// cache: res!.headers.get('cf-cache-status'),
// status: res!.status,
// timestamp: Math.ceil(now / 1000),
// computeTime: originTimespan!,
// gatewayTime: gatewayTimespan!,
// // TODO: record correct bandwidth of request + response content-length
// bandwidth: 0
// })
// )
}
} catch (err: any) {
console.error(err)
@ -139,8 +143,7 @@ export default {
code: err.code
}),
{
status: 500,
headers: globalResHeaders
status: 500
}
)
}

Wyświetl plik

@ -52,7 +52,7 @@
"prod": {
"vars": {
"ENVIRONMENT": "production",
"AGENTIC_API_BASE_URL": "https://api.agentic.ai"
"AGENTIC_API_BASE_URL": "https://api.agentic.so"
}
}
}

Wyświetl plik

@ -25,6 +25,12 @@ export default [
'unicorn/no-process-exit': 'off'
}
},
{
files: ['apps/gateway/src/**/*.ts'],
rules: {
'no-console': 'off'
}
},
{
files: ['apps/api/src/**/*.ts'],
plugins: {

Wyświetl plik

@ -21,6 +21,11 @@ export function mergeJsonSchemaObjects(
label: string
}
) {
assert(
source !== 'cookie',
'Cookie parameters for OpenAPI operations are not yet supported. If you need cookie parameter support, please contact support@agentic.so.'
)
if (schema1.type === 'object' && schema1.properties) {
schema0.properties = {
...schema0.properties,