feat: clean up gateway resolving edge request logic

pull/715/head
Travis Fischer 2025-06-13 07:00:05 +07:00
rodzic b066de9c5f
commit b61641240e
10 zmienionych plików z 202 dodań i 188 usunięć

Wyświetl plik

@ -37,6 +37,7 @@
"@agentic/platform-hono": "workspace:*", "@agentic/platform-hono": "workspace:*",
"@agentic/platform-types": "workspace:*", "@agentic/platform-types": "workspace:*",
"@agentic/platform-validators": "workspace:*", "@agentic/platform-validators": "workspace:*",
"@cloudflare/workers-oauth-provider": "^0.0.5",
"@hono/zod-validator": "catalog:", "@hono/zod-validator": "catalog:",
"@modelcontextprotocol/sdk": "catalog:", "@modelcontextprotocol/sdk": "catalog:",
"@sentry/cloudflare": "catalog:", "@sentry/cloudflare": "catalog:",

Wyświetl plik

@ -7,17 +7,18 @@ import {
responseTime, responseTime,
sentry sentry
} from '@agentic/platform-hono' } from '@agentic/platform-hono'
import { parseToolIdentifier } from '@agentic/platform-validators'
import { Hono } from 'hono' import { Hono } from 'hono'
import type { GatewayHonoEnv, ResolvedOriginToolCallResult } from './lib/types' import type {
GatewayHonoEnv,
ResolvedHttpEdgeRequest,
ResolvedOriginToolCallResult
} from './lib/types'
import { createAgenticClient } from './lib/agentic-client' import { createAgenticClient } from './lib/agentic-client'
import { createHttpResponseFromMcpToolCallResponse } from './lib/create-http-response-from-mcp-tool-call-response' import { createHttpResponseFromMcpToolCallResponse } from './lib/create-http-response-from-mcp-tool-call-response'
import { recordToolCallUsage } from './lib/record-tool-call-usage' import { recordToolCallUsage } from './lib/record-tool-call-usage'
import { import { resolveEdgeRequest } from './lib/resolve-edge-request'
type ResolvedHttpEdgeRequest, import { resolveHttpEdgeRequest } from './lib/resolve-http-edge-request'
resolveHttpEdgeRequest
} from './lib/resolve-http-edge-request'
import { resolveMcpEdgeRequest } from './lib/resolve-mcp-edge-request' import { resolveMcpEdgeRequest } from './lib/resolve-mcp-edge-request'
import { resolveOriginToolCall } from './lib/resolve-origin-tool-call' import { resolveOriginToolCall } from './lib/resolve-origin-tool-call'
import { isRequestPubliclyCacheable } from './lib/utils' import { isRequestPubliclyCacheable } from './lib/utils'
@ -65,21 +66,17 @@ app.all(async (ctx) => {
}) })
) )
// TODO: Clean up the duplication between this block, const resolvedEdgeRequest = await resolveEdgeRequest(ctx)
// `resolveMcpEdgeRequest`, and `resolveHttpEdgeRequest`. const { toolName } = resolvedEdgeRequest.parsedToolIdentifier
const requestUrl = new URL(ctx.req.url)
const { pathname } = requestUrl
const requestedToolIdentifier = pathname.replace(/^\//, '').replace(/\/$/, '')
const { toolName } = parseToolIdentifier(requestedToolIdentifier)
// Handle MCP requests // Handle MCP requests
if (toolName === 'mcp') { if (toolName === 'mcp') {
ctx.set('isJsonRpcRequest', true) ctx.set('isJsonRpcRequest', true)
const executionCtx = ctx.executionCtx as any const executionCtx = ctx.executionCtx as any
const mcpInfo = await resolveMcpEdgeRequest(ctx) const mcpInfo = await resolveMcpEdgeRequest(ctx, resolvedEdgeRequest)
executionCtx.props = mcpInfo executionCtx.props = mcpInfo
return DurableMcpServer.serve(pathname, { return DurableMcpServer.serve('/*', {
binding: 'DO_MCP_SERVER' binding: 'DO_MCP_SERVER'
}).fetch(ctx.req.raw, ctx.env, executionCtx) }).fetch(ctx.req.raw, ctx.env, executionCtx)
} }
@ -92,14 +89,16 @@ app.all(async (ctx) => {
try { try {
// Resolve the http edge request to a specific deployment, consumer, and // Resolve the http edge request to a specific deployment, consumer, and
// tool call. // tool call.
resolvedHttpEdgeRequest = await resolveHttpEdgeRequest(ctx) resolvedHttpEdgeRequest = await resolveHttpEdgeRequest(
ctx,
resolvedEdgeRequest
)
// Invoke the origin tool call. // Invoke the origin tool call.
resolvedOriginToolCallResult = await resolveOriginToolCall({ resolvedOriginToolCallResult = await resolveOriginToolCall({
...resolvedHttpEdgeRequest, ...resolvedHttpEdgeRequest,
args: resolvedHttpEdgeRequest.toolCallArgs, args: resolvedHttpEdgeRequest.toolCallArgs,
sessionId: ctx.get('sessionId')!, sessionId: ctx.get('sessionId')!,
ip: ctx.get('ip'),
env: ctx.env, env: ctx.env,
waitUntil waitUntil
}) })
@ -129,12 +128,10 @@ app.all(async (ctx) => {
if (resolvedHttpEdgeRequest && res) { if (resolvedHttpEdgeRequest && res) {
recordToolCallUsage({ recordToolCallUsage({
...resolvedHttpEdgeRequest, ...resolvedHttpEdgeRequest,
requestMode: 'http', edgeRequestMode: 'http',
httpResponse: res, httpResponse: res,
resolvedOriginToolCallResult, resolvedOriginToolCallResult,
sessionId: ctx.get('sessionId')!, sessionId: ctx.get('sessionId')!,
requestId: ctx.get('requestId')!,
ip: ctx.get('ip'),
env: ctx.env, env: ctx.env,
waitUntil waitUntil
}) })

Wyświetl plik

@ -1,4 +1,3 @@
import type { AdminDeployment, PricingPlan } from '@agentic/platform-types'
import { assert, getRateLimitHeaders } from '@agentic/platform-core' import { assert, getRateLimitHeaders } from '@agentic/platform-core'
import { parseDeploymentIdentifier } from '@agentic/platform-validators' import { parseDeploymentIdentifier } from '@agentic/platform-validators'
import { Server } from '@modelcontextprotocol/sdk/server/index.js' import { Server } from '@modelcontextprotocol/sdk/server/index.js'
@ -11,8 +10,8 @@ import { McpAgent } from 'agents/mcp'
import type { RawEnv } from './env' import type { RawEnv } from './env'
import type { import type {
AdminConsumer,
McpToolCallResponse, McpToolCallResponse,
ResolvedMcpEdgeRequest,
ResolvedOriginToolCallResult ResolvedOriginToolCallResult
} from './types' } from './types'
import { handleMcpToolCallError } from './handle-mcp-tool-call-error' import { handleMcpToolCallError } from './handle-mcp-tool-call-error'
@ -23,13 +22,8 @@ import { createAgenticMcpMetadata } from './utils'
export class DurableMcpServerBase extends McpAgent< export class DurableMcpServerBase extends McpAgent<
RawEnv, RawEnv,
never, // TODO: do we need local state? never, // We aren't currently using local state, so set it to `never`.
{ ResolvedMcpEdgeRequest
deployment: AdminDeployment
consumer?: AdminConsumer
pricingPlan?: PricingPlan
ip?: string
}
> { > {
protected _serverP = Promise.withResolvers<Server>() protected _serverP = Promise.withResolvers<Server>()
override server = this._serverP.promise override server = this._serverP.promise
@ -40,7 +34,7 @@ export class DurableMcpServerBase extends McpAgent<
} }
override async init() { override async init() {
const { consumer, deployment, pricingPlan, ip } = this.props const { consumer, deployment, pricingPlan } = this.props
const { projectIdentifier } = parseDeploymentIdentifier( const { projectIdentifier } = parseDeploymentIdentifier(
deployment.identifier deployment.identifier
) )
@ -62,20 +56,17 @@ export class DurableMcpServerBase extends McpAgent<
) )
if (toolConfig) { if (toolConfig) {
const pricingPlanToolConfig = pricingPlan const pricingPlanToolOverride = pricingPlan
? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug] ? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug]
: undefined : undefined
if (pricingPlanToolConfig?.enabled === false) { if (pricingPlanToolOverride?.enabled === true) {
// Tool is disabled / hidden for the customer's current pricing plan // Tool is explicitly enabled for the customer's pricing plan
} else if (pricingPlanToolOverride?.enabled === false) {
// Tool is disabled for the customer's pricing plan
return undefined return undefined
} } else if (toolConfig.enabled === false) {
// Tool is disabled for all pricing plans
if (
pricingPlanToolConfig?.enabled !== true &&
toolConfig.enabled === false
) {
// Tool is disabled / hidden for all pricing plans
return undefined return undefined
} }
} }
@ -101,15 +92,12 @@ export class DurableMcpServerBase extends McpAgent<
assert(tool, 404, `Unknown tool "${toolName}"`) assert(tool, 404, `Unknown tool "${toolName}"`)
resolvedOriginToolCallResult = await resolveOriginToolCall({ resolvedOriginToolCallResult = await resolveOriginToolCall({
...this.props,
tool, tool,
args, args,
deployment,
consumer,
pricingPlan,
cacheControl, cacheControl,
sessionId, sessionId,
env: this.env, env: this.env,
ip,
waitUntil: this.ctx.waitUntil.bind(this.ctx) waitUntil: this.ctx.waitUntil.bind(this.ctx)
}) })
@ -155,13 +143,11 @@ export class DurableMcpServerBase extends McpAgent<
// Record tool call usage, whether the call was successful or not. // Record tool call usage, whether the call was successful or not.
recordToolCallUsage({ recordToolCallUsage({
...this.props, ...this.props,
requestMode: 'mcp', edgeRequestMode: 'mcp',
tool, tool,
mcpToolCallResponse: toolCallResponse!, mcpToolCallResponse: toolCallResponse!,
resolvedOriginToolCallResult, resolvedOriginToolCallResult,
sessionId, sessionId,
// TODO: requestId
ip,
env: this.env, env: this.env,
waitUntil: this.ctx.waitUntil.bind(this.ctx) waitUntil: this.ctx.waitUntil.bind(this.ctx)
}) })

Wyświetl plik

@ -7,8 +7,8 @@ import type {
import type { RawEnv } from './env' import type { RawEnv } from './env'
import type { import type {
AdminConsumer, AdminConsumer,
EdgeRequestMode,
McpToolCallResponse, McpToolCallResponse,
RequestMode,
ResolvedOriginToolCallResult, ResolvedOriginToolCallResult,
WaitUntil WaitUntil
} from './types' } from './types'
@ -31,7 +31,7 @@ import { createStripe } from './external/stripe'
* @see https://developers.cloudflare.com/analytics/analytics-engine/limits/ * @see https://developers.cloudflare.com/analytics/analytics-engine/limits/
*/ */
export function recordToolCallUsage({ export function recordToolCallUsage({
requestMode, edgeRequestMode,
deployment, deployment,
consumer, consumer,
tool, tool,
@ -44,7 +44,7 @@ export function recordToolCallUsage({
env, env,
waitUntil waitUntil
}: { }: {
requestMode: RequestMode edgeRequestMode: EdgeRequestMode
deployment: AdminDeployment deployment: AdminDeployment
consumer?: AdminConsumer consumer?: AdminConsumer
pricingPlan?: PricingPlan pricingPlan?: PricingPlan
@ -60,13 +60,13 @@ export function recordToolCallUsage({
} & ( } & (
| { | {
// For http requests, an http response is required. // For http requests, an http response is required.
requestMode: 'http' edgeRequestMode: 'http'
httpResponse: Response httpResponse: Response
mcpToolCallResponse?: never mcpToolCallResponse?: never
} }
| { | {
// For mcp cool call requests, an mcp tool call response is required. // For mcp cool call requests, an mcp tool call response is required.
requestMode: 'mcp' edgeRequestMode: 'mcp'
httpResponse?: never httpResponse?: never
mcpToolCallResponse: McpToolCallResponse mcpToolCallResponse: McpToolCallResponse
} }
@ -105,7 +105,7 @@ export function recordToolCallUsage({
tool?.name ?? null, tool?.name ?? null,
// Whether this request was made via MCP or HTTP // Whether this request was made via MCP or HTTP
requestMode, edgeRequestMode,
// IP address or session ID // IP address or session ID
ip ?? sessionId, ip ?? sessionId,

Wyświetl plik

@ -0,0 +1,91 @@
import type { AdminDeployment, PricingPlan } from '@agentic/platform-types'
import { assert } from '@agentic/platform-core'
import { parseToolIdentifier } from '@agentic/platform-validators'
import type {
AdminConsumer,
GatewayHonoContext,
ResolvedEdgeRequest
} from './types'
import { getAdminConsumer } from './get-admin-consumer'
import { getAdminDeployment } from './get-admin-deployment'
/**
* Resolves an input HTTP request to a specific deployment.
*/
export async function resolveEdgeRequest(
ctx: GatewayHonoContext
): Promise<ResolvedEdgeRequest> {
const requestUrl = new URL(ctx.req.url)
const { pathname } = requestUrl
const requestedToolIdentifier = pathname.replace(/^\//, '').replace(/\/$/, '')
const parsedToolIdentifier = parseToolIdentifier(requestedToolIdentifier)
const deployment = await getAdminDeployment(
ctx,
parsedToolIdentifier.deploymentIdentifier
)
return {
parsedToolIdentifier,
deployment,
requestId: ctx.get('requestId'),
ip: ctx.get('ip')
}
}
/**
* Resolves a consumer and pricing plan for an edge request.
*/
export async function resolveConsumerForEdgeRequest(
ctx: GatewayHonoContext,
{
deployment,
apiKey
}: {
deployment: AdminDeployment
apiKey?: string
}
): Promise<{
consumer?: AdminConsumer
pricingPlan?: PricingPlan
}> {
let pricingPlan: PricingPlan | undefined
let consumer: AdminConsumer | undefined
if (apiKey) {
consumer = await getAdminConsumer(ctx, apiKey)
assert(consumer, 401, `Invalid API key "${apiKey}"`)
assert(
consumer.isStripeSubscriptionActive,
402,
`API key "${apiKey}" does not have an active subscription`
)
assert(
consumer.projectId === deployment.projectId,
403,
`API key "${apiKey}" is not authorized for project "${deployment.projectId}"`
)
// TODO: Ensure that consumer.plan is compatible with the target deployment?
// TODO: This could definitely cause issues when changing pricing plans.
pricingPlan = deployment.pricingPlans.find(
(pricingPlan) => consumer!.plan === pricingPlan.slug
)
// assert(
// pricingPlan,
// 403,
// `Auth token "${token}" unable to find matching pricing plan for project "${deployment.project}"`
// )
} else {
// For unauthenticated requests, default to a free pricing plan if available.
pricingPlan = deployment.pricingPlans.find((plan) => plan.slug === 'free')
}
return {
consumer,
pricingPlan
}
}

Wyświetl plik

@ -1,37 +1,22 @@
import type {
AdminDeployment,
PricingPlan,
Tool
} from '@agentic/platform-types'
import { assert } from '@agentic/platform-core' import { assert } from '@agentic/platform-core'
import { parseToolIdentifier } from '@agentic/platform-validators'
import type { AdminConsumer, GatewayHonoContext, ToolCallArgs } from './types' import type {
import { getAdminConsumer } from './get-admin-consumer' GatewayHonoContext,
import { getAdminDeployment } from './get-admin-deployment' ResolvedEdgeRequest,
ResolvedHttpEdgeRequest
} from './types'
import { getTool } from './get-tool' import { getTool } from './get-tool'
import { getToolArgsFromRequest } from './get-tool-args-from-request' import { getToolArgsFromRequest } from './get-tool-args-from-request'
import { resolveConsumerForEdgeRequest } from './resolve-edge-request'
import { isRequestPubliclyCacheable } from './utils' import { isRequestPubliclyCacheable } from './utils'
export type ResolvedHttpEdgeRequest = {
deployment: AdminDeployment
consumer?: AdminConsumer
pricingPlan?: PricingPlan
tool: Tool
toolCallArgs: ToolCallArgs
cacheControl?: string
}
/** /**
* Resolves an input HTTP request to a specific deployment, tool call, and * Resolves an input HTTP request to a specific deployment, tool call, consumer,
* billing subscription. * and pricing plan.
*
* Also ensures that the request is valid, enforces rate limits, and adds proxy-
* specific headers to the origin request.
*/ */
export async function resolveHttpEdgeRequest( export async function resolveHttpEdgeRequest(
ctx: GatewayHonoContext ctx: GatewayHonoContext,
resolvedEdgeRequest: ResolvedEdgeRequest
): Promise<ResolvedHttpEdgeRequest> { ): Promise<ResolvedHttpEdgeRequest> {
const logger = ctx.get('logger') const logger = ctx.get('logger')
const ip = ctx.get('ip') const ip = ctx.get('ip')
@ -40,15 +25,9 @@ export async function resolveHttpEdgeRequest(
? ctx.req.header('cache-control') ? ctx.req.header('cache-control')
: 'no-store' : 'no-store'
const { deployment, parsedToolIdentifier } = resolvedEdgeRequest
const { toolName } = parsedToolIdentifier
const { method } = ctx.req const { method } = ctx.req
const requestUrl = new URL(ctx.req.url)
const { pathname } = requestUrl
const requestedToolIdentifier = pathname.replace(/^\//, '').replace(/\/$/, '')
const { toolName, deploymentIdentifier } = parseToolIdentifier(
requestedToolIdentifier
)
const deployment = await getAdminDeployment(ctx, deploymentIdentifier)
const tool = getTool({ const tool = getTool({
method, method,
@ -58,66 +37,36 @@ export async function resolveHttpEdgeRequest(
logger.debug('request', { logger.debug('request', {
method, method,
pathname,
deploymentIdentifier: deployment.identifier, deploymentIdentifier: deployment.identifier,
toolName, toolName,
tool tool
}) })
let pricingPlan: PricingPlan | undefined const apiKey = (ctx.req.header('authorization') || '')
let consumer: AdminConsumer | undefined
const token = (ctx.req.header('authorization') || '')
.replace(/^Bearer /i, '') .replace(/^Bearer /i, '')
.trim() .trim()
if (token) { const { consumer, pricingPlan } = await resolveConsumerForEdgeRequest(ctx, {
consumer = await getAdminConsumer(ctx, token) deployment,
assert(consumer, 401, `Invalid auth token "${token}"`) apiKey
assert( })
consumer.isStripeSubscriptionActive,
402,
`Auth token "${token}" does not have an active subscription`
)
assert(
consumer.projectId === deployment.projectId,
403,
`Auth token "${token}" is not authorized for project "${deployment.projectId}"`
)
// TODO: Ensure that consumer.plan is compatible with the target deployment?
// TODO: This could definitely cause issues when changing pricing plans.
pricingPlan = deployment.pricingPlans.find(
(pricingPlan) => consumer!.plan === pricingPlan.slug
)
// assert(
// pricingPlan,
// 403,
// `Auth token "${token}" unable to find matching pricing plan for project "${deployment.project}"`
// )
if (consumer) {
if (!ctx.get('sessionId')) { if (!ctx.get('sessionId')) {
ctx.set('sessionId', `${consumer.id}:${deployment.id}`) ctx.set('sessionId', `${consumer.id}:${deployment.id}`)
} }
} else { } else {
// For unauthenticated requests, default to a free pricing plan if available.
pricingPlan = deployment.pricingPlans.find((plan) => plan.slug === 'free')
if (!ctx.get('sessionId')) { if (!ctx.get('sessionId')) {
assert(ip, 500, 'IP address is required for unauthenticated requests') assert(ip, 500, 'IP address is required for unauthenticated requests')
ctx.set('sessionId', `${ip}:${deployment.projectId}`) ctx.set('sessionId', `${ip}:${deployment.projectId}`)
} }
} }
assert(ctx.get('sessionId'), 500, 'Internal error: sessionId should be set') // Parse tool call arguments from the request body.
// Parse tool call args from the request body.
const toolCallArgs = await getToolArgsFromRequest(ctx, { tool, deployment }) const toolCallArgs = await getToolArgsFromRequest(ctx, { tool, deployment })
return { return {
deployment, ...resolvedEdgeRequest,
consumer, consumer,
pricingPlan, pricingPlan,
tool, tool,

Wyświetl plik

@ -1,71 +1,27 @@
import type { AdminDeployment, PricingPlan } from '@agentic/platform-types' import type {
import { assert } from '@agentic/platform-core' GatewayHonoContext,
import { parseToolIdentifier } from '@agentic/platform-validators' ResolvedEdgeRequest,
ResolvedMcpEdgeRequest
import type { AdminConsumer, GatewayHonoContext } from './types' } from './types'
import { getAdminConsumer } from './get-admin-consumer' import { resolveConsumerForEdgeRequest } from './resolve-edge-request'
import { getAdminDeployment } from './get-admin-deployment'
export type ResolvedMcpEdgeRequest = {
deployment: AdminDeployment
consumer?: AdminConsumer
pricingPlan?: PricingPlan
ip?: string
}
export async function resolveMcpEdgeRequest( export async function resolveMcpEdgeRequest(
ctx: GatewayHonoContext ctx: GatewayHonoContext,
resolvedEdgeRequest: ResolvedEdgeRequest
): Promise<ResolvedMcpEdgeRequest> { ): Promise<ResolvedMcpEdgeRequest> {
const requestUrl = new URL(ctx.req.url) const { deployment } = resolvedEdgeRequest
const { pathname } = requestUrl
const requestedDeploymentIdentifier = pathname
.replace(/^\//, '')
.replace(/\/$/, '')
const { deploymentIdentifier } = parseToolIdentifier(
requestedDeploymentIdentifier
)
const deployment = await getAdminDeployment(ctx, deploymentIdentifier)
// TODO: Should MCP edge requests also support Authorization header?
const apiKey = ctx.req.query('apiKey')?.trim() const apiKey = ctx.req.query('apiKey')?.trim()
let consumer: AdminConsumer | undefined
let pricingPlan: PricingPlan | undefined
if (apiKey) { const { consumer, pricingPlan } = await resolveConsumerForEdgeRequest(ctx, {
consumer = await getAdminConsumer(ctx, apiKey) deployment,
assert(consumer, 401, `Invalid api key "${apiKey}"`) apiKey
assert( })
consumer.isStripeSubscriptionActive,
402,
`API key "${apiKey}" subscription is not active`
)
assert(
consumer.projectId === deployment.projectId,
403,
`API key "${apiKey}" is not authorized for project "${deployment.projectId}"`
)
// TODO: Ensure that consumer.plan is compatible with the target deployment?
// TODO: This could definitely cause issues when changing pricing plans.
pricingPlan = deployment.pricingPlans.find(
(pricingPlan) => consumer!.plan === pricingPlan.slug
)
// assert(
// pricingPlan,
// 403,
// `API key "${apiKey}" unable to find matching pricing plan for project "${deployment.project}"`
// )
} else {
// For unauthenticated requests, default to a free pricing plan if available.
pricingPlan = deployment.pricingPlans.find((plan) => plan.slug === 'free')
}
return { return {
deployment, ...resolvedEdgeRequest,
consumer, consumer,
pricingPlan, pricingPlan
ip: ctx.get('ip')
} }
} }

Wyświetl plik

@ -123,7 +123,7 @@ export async function resolveOriginToolCall({
const pricingPlanToolOverride = pricingPlan const pricingPlanToolOverride = pricingPlan
? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug] ? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug]
: undefined : undefined
const isToolConfigEnabled = toolConfig.enabled ?? true const isToolEnabled = toolConfig.enabled ?? true
// Check if this tool is configured for pricing-plan-specific overrides // Check if this tool is configured for pricing-plan-specific overrides
// which take precedence over the tool's default behavior. // which take precedence over the tool's default behavior.
@ -131,11 +131,11 @@ export async function resolveOriginToolCall({
if (pricingPlanToolOverride.enabled !== undefined) { if (pricingPlanToolOverride.enabled !== undefined) {
assert( assert(
pricingPlanToolOverride.enabled, pricingPlanToolOverride.enabled,
isToolConfigEnabled ? 403 : 404, isToolEnabled ? 403 : 404,
`Tool "${tool.name}" is disabled for pricing plan "${pricingPlan.slug}"` `Tool "${tool.name}" is disabled for pricing plan "${pricingPlan.slug}"`
) )
} else { } else {
assert(isToolConfigEnabled, 404, `Tool "${tool.name}" is disabled`) assert(isToolEnabled, 404, `Tool "${tool.name}" is disabled`)
} }
if (pricingPlanToolOverride.reportUsage !== undefined) { if (pricingPlanToolOverride.reportUsage !== undefined) {
@ -146,7 +146,7 @@ export async function resolveOriginToolCall({
rateLimit = pricingPlanToolOverride.rateLimit rateLimit = pricingPlanToolOverride.rateLimit
} }
} else { } else {
assert(isToolConfigEnabled, 404, `Tool "${tool.name}" is disabled`) assert(isToolEnabled, 404, `Tool "${tool.name}" is disabled`)
} }
} else { } else {
if (cacheControl) { if (cacheControl) {

Wyświetl plik

@ -6,10 +6,14 @@ import type {
} from '@agentic/platform-hono' } from '@agentic/platform-hono'
import type { import type {
AdminConsumer as AdminConsumerImpl, AdminConsumer as AdminConsumerImpl,
AdminDeployment,
PricingPlan,
RateLimit, RateLimit,
Tool,
ToolConfig, ToolConfig,
User User
} from '@agentic/platform-types' } from '@agentic/platform-types'
import type { ParsedToolIdentifier } from '@agentic/platform-validators'
import type { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js' import type { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js'
import type { Context } from 'hono' import type { Context } from 'hono'
import type { Simplify } from 'type-fest' import type { Simplify } from 'type-fest'
@ -57,10 +61,30 @@ export type RateLimitState = {
export type RateLimitCache = Map<string, RateLimitState> export type RateLimitCache = Map<string, RateLimitState>
export type CacheStatus = 'HIT' | 'MISS' | 'BYPASS' | 'DYNAMIC' export type CacheStatus = 'HIT' | 'MISS' | 'BYPASS' | 'DYNAMIC'
export type RequestMode = 'mcp' | 'http' export type EdgeRequestMode = 'mcp' | 'http'
export type WaitUntil = (promise: Promise<any>) => void export type WaitUntil = (promise: Promise<any>) => void
export interface ResolvedEdgeRequest extends Record<string, unknown> {
parsedToolIdentifier: ParsedToolIdentifier
deployment: AdminDeployment
requestId: string
ip?: string
}
export interface ResolvedMcpEdgeRequest extends ResolvedEdgeRequest {
consumer?: AdminConsumer
pricingPlan?: PricingPlan
}
export interface ResolvedHttpEdgeRequest extends ResolvedEdgeRequest {
consumer?: AdminConsumer
pricingPlan?: PricingPlan
tool: Tool
toolCallArgs: ToolCallArgs
cacheControl?: string
}
export type ResolvedOriginToolCallResult = { export type ResolvedOriginToolCallResult = {
toolCallArgs: ToolCallArgs toolCallArgs: ToolCallArgs
originRequest?: Request originRequest?: Request

Wyświetl plik

@ -463,6 +463,9 @@ importers:
'@agentic/platform-validators': '@agentic/platform-validators':
specifier: workspace:* specifier: workspace:*
version: link:../../packages/validators version: link:../../packages/validators
'@cloudflare/workers-oauth-provider':
specifier: ^0.0.5
version: 0.0.5
'@hono/zod-validator': '@hono/zod-validator':
specifier: 'catalog:' specifier: 'catalog:'
version: 0.7.0(hono@4.7.11)(zod@3.25.62) version: 0.7.0(hono@4.7.11)(zod@3.25.62)
@ -969,6 +972,9 @@ packages:
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@cloudflare/workers-oauth-provider@0.0.5':
resolution: {integrity: sha512-t1x5KAzsubCvb4APnJ93z407X1x7SGj/ga5ziRnwIb/iLy4PMkT/hgd1y5z7Bbsdy5Fy6mywhCP4lym24bX66w==}
'@cloudflare/workers-types@4.20250610.0': '@cloudflare/workers-types@4.20250610.0':
resolution: {integrity: sha512-HxnUoey3QxCEfy07pUm7J42jBi9YPHq/hA3fw6JmOqYLHdviHI28OA8lup+2RUaHwDzh6q1DSfrBvvDqde645A==} resolution: {integrity: sha512-HxnUoey3QxCEfy07pUm7J42jBi9YPHq/hA3fw6JmOqYLHdviHI28OA8lup+2RUaHwDzh6q1DSfrBvvDqde645A==}
@ -5986,6 +5992,10 @@ snapshots:
'@cloudflare/workerd-windows-64@1.20250604.0': '@cloudflare/workerd-windows-64@1.20250604.0':
optional: true optional: true
'@cloudflare/workers-oauth-provider@0.0.5':
dependencies:
'@cloudflare/workers-types': 4.20250610.0
'@cloudflare/workers-types@4.20250610.0': {} '@cloudflare/workers-types@4.20250610.0': {}
'@commander-js/extra-typings@14.0.0(commander@14.0.0)': '@commander-js/extra-typings@14.0.0(commander@14.0.0)':