From b61641240e01730833b802a0873bc9a261148aa3 Mon Sep 17 00:00:00 2001 From: Travis Fischer Date: Fri, 13 Jun 2025 07:00:05 +0700 Subject: [PATCH] feat: clean up gateway resolving edge request logic --- apps/gateway/package.json | 1 + apps/gateway/src/app.ts | 35 ++++--- apps/gateway/src/lib/durable-mcp-server.ts | 40 +++----- .../gateway/src/lib/record-tool-call-usage.ts | 12 +-- apps/gateway/src/lib/resolve-edge-request.ts | 91 +++++++++++++++++++ .../src/lib/resolve-http-edge-request.ts | 91 ++++--------------- .../src/lib/resolve-mcp-edge-request.ts | 76 ++++------------ .../src/lib/resolve-origin-tool-call.ts | 8 +- apps/gateway/src/lib/types.ts | 26 +++++- pnpm-lock.yaml | 10 ++ 10 files changed, 202 insertions(+), 188 deletions(-) create mode 100644 apps/gateway/src/lib/resolve-edge-request.ts diff --git a/apps/gateway/package.json b/apps/gateway/package.json index 8bfa1e2c..06550c6b 100644 --- a/apps/gateway/package.json +++ b/apps/gateway/package.json @@ -37,6 +37,7 @@ "@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:", diff --git a/apps/gateway/src/app.ts b/apps/gateway/src/app.ts index 52c47b52..3715fafc 100644 --- a/apps/gateway/src/app.ts +++ b/apps/gateway/src/app.ts @@ -7,17 +7,18 @@ import { responseTime, sentry } from '@agentic/platform-hono' -import { parseToolIdentifier } from '@agentic/platform-validators' 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 { createHttpResponseFromMcpToolCallResponse } from './lib/create-http-response-from-mcp-tool-call-response' import { recordToolCallUsage } from './lib/record-tool-call-usage' -import { - type ResolvedHttpEdgeRequest, - resolveHttpEdgeRequest -} from './lib/resolve-http-edge-request' +import { resolveEdgeRequest } from './lib/resolve-edge-request' +import { resolveHttpEdgeRequest } from './lib/resolve-http-edge-request' import { resolveMcpEdgeRequest } from './lib/resolve-mcp-edge-request' import { resolveOriginToolCall } from './lib/resolve-origin-tool-call' import { isRequestPubliclyCacheable } from './lib/utils' @@ -65,21 +66,17 @@ app.all(async (ctx) => { }) ) - // TODO: Clean up the duplication between this block, - // `resolveMcpEdgeRequest`, and `resolveHttpEdgeRequest`. - const requestUrl = new URL(ctx.req.url) - const { pathname } = requestUrl - const requestedToolIdentifier = pathname.replace(/^\//, '').replace(/\/$/, '') - const { toolName } = parseToolIdentifier(requestedToolIdentifier) + const resolvedEdgeRequest = await resolveEdgeRequest(ctx) + const { toolName } = resolvedEdgeRequest.parsedToolIdentifier // Handle MCP requests if (toolName === 'mcp') { ctx.set('isJsonRpcRequest', true) const executionCtx = ctx.executionCtx as any - const mcpInfo = await resolveMcpEdgeRequest(ctx) + const mcpInfo = await resolveMcpEdgeRequest(ctx, resolvedEdgeRequest) executionCtx.props = mcpInfo - return DurableMcpServer.serve(pathname, { + return DurableMcpServer.serve('/*', { binding: 'DO_MCP_SERVER' }).fetch(ctx.req.raw, ctx.env, executionCtx) } @@ -92,14 +89,16 @@ app.all(async (ctx) => { try { // Resolve the http edge request to a specific deployment, consumer, and // tool call. - resolvedHttpEdgeRequest = await resolveHttpEdgeRequest(ctx) + resolvedHttpEdgeRequest = await resolveHttpEdgeRequest( + ctx, + resolvedEdgeRequest + ) // Invoke the origin tool call. resolvedOriginToolCallResult = await resolveOriginToolCall({ ...resolvedHttpEdgeRequest, args: resolvedHttpEdgeRequest.toolCallArgs, sessionId: ctx.get('sessionId')!, - ip: ctx.get('ip'), env: ctx.env, waitUntil }) @@ -129,12 +128,10 @@ app.all(async (ctx) => { if (resolvedHttpEdgeRequest && res) { recordToolCallUsage({ ...resolvedHttpEdgeRequest, - requestMode: 'http', + edgeRequestMode: 'http', httpResponse: res, resolvedOriginToolCallResult, sessionId: ctx.get('sessionId')!, - requestId: ctx.get('requestId')!, - ip: ctx.get('ip'), env: ctx.env, waitUntil }) diff --git a/apps/gateway/src/lib/durable-mcp-server.ts b/apps/gateway/src/lib/durable-mcp-server.ts index 9909a324..96bbd253 100644 --- a/apps/gateway/src/lib/durable-mcp-server.ts +++ b/apps/gateway/src/lib/durable-mcp-server.ts @@ -1,4 +1,3 @@ -import type { AdminDeployment, PricingPlan } from '@agentic/platform-types' import { assert, getRateLimitHeaders } from '@agentic/platform-core' import { parseDeploymentIdentifier } from '@agentic/platform-validators' import { Server } from '@modelcontextprotocol/sdk/server/index.js' @@ -11,8 +10,8 @@ import { McpAgent } from 'agents/mcp' import type { RawEnv } from './env' import type { - AdminConsumer, McpToolCallResponse, + ResolvedMcpEdgeRequest, ResolvedOriginToolCallResult } from './types' import { handleMcpToolCallError } from './handle-mcp-tool-call-error' @@ -23,13 +22,8 @@ import { createAgenticMcpMetadata } from './utils' export class DurableMcpServerBase extends McpAgent< RawEnv, - never, // TODO: do we need local state? - { - deployment: AdminDeployment - consumer?: AdminConsumer - pricingPlan?: PricingPlan - ip?: string - } + never, // We aren't currently using local state, so set it to `never`. + ResolvedMcpEdgeRequest > { protected _serverP = Promise.withResolvers() override server = this._serverP.promise @@ -40,7 +34,7 @@ export class DurableMcpServerBase extends McpAgent< } override async init() { - const { consumer, deployment, pricingPlan, ip } = this.props + const { consumer, deployment, pricingPlan } = this.props const { projectIdentifier } = parseDeploymentIdentifier( deployment.identifier ) @@ -62,20 +56,17 @@ export class DurableMcpServerBase extends McpAgent< ) if (toolConfig) { - const pricingPlanToolConfig = pricingPlan + const pricingPlanToolOverride = pricingPlan ? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug] : undefined - if (pricingPlanToolConfig?.enabled === false) { - // Tool is disabled / hidden for the customer's current pricing plan + if (pricingPlanToolOverride?.enabled === true) { + // 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 - } - - if ( - pricingPlanToolConfig?.enabled !== true && - toolConfig.enabled === false - ) { - // Tool is disabled / hidden for all pricing plans + } else if (toolConfig.enabled === false) { + // Tool is disabled for all pricing plans return undefined } } @@ -101,15 +92,12 @@ export class DurableMcpServerBase extends McpAgent< assert(tool, 404, `Unknown tool "${toolName}"`) resolvedOriginToolCallResult = await resolveOriginToolCall({ + ...this.props, tool, args, - deployment, - consumer, - pricingPlan, cacheControl, sessionId, env: this.env, - ip, 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. recordToolCallUsage({ ...this.props, - requestMode: 'mcp', + edgeRequestMode: 'mcp', tool, mcpToolCallResponse: toolCallResponse!, resolvedOriginToolCallResult, sessionId, - // TODO: requestId - ip, env: this.env, waitUntil: this.ctx.waitUntil.bind(this.ctx) }) diff --git a/apps/gateway/src/lib/record-tool-call-usage.ts b/apps/gateway/src/lib/record-tool-call-usage.ts index a9685361..c47f041a 100644 --- a/apps/gateway/src/lib/record-tool-call-usage.ts +++ b/apps/gateway/src/lib/record-tool-call-usage.ts @@ -7,8 +7,8 @@ import type { import type { RawEnv } from './env' import type { AdminConsumer, + EdgeRequestMode, McpToolCallResponse, - RequestMode, ResolvedOriginToolCallResult, WaitUntil } from './types' @@ -31,7 +31,7 @@ import { createStripe } from './external/stripe' * @see https://developers.cloudflare.com/analytics/analytics-engine/limits/ */ export function recordToolCallUsage({ - requestMode, + edgeRequestMode, deployment, consumer, tool, @@ -44,7 +44,7 @@ export function recordToolCallUsage({ env, waitUntil }: { - requestMode: RequestMode + edgeRequestMode: EdgeRequestMode deployment: AdminDeployment consumer?: AdminConsumer pricingPlan?: PricingPlan @@ -60,13 +60,13 @@ export function recordToolCallUsage({ } & ( | { // For http requests, an http response is required. - requestMode: 'http' + edgeRequestMode: 'http' httpResponse: Response mcpToolCallResponse?: never } | { // For mcp cool call requests, an mcp tool call response is required. - requestMode: 'mcp' + edgeRequestMode: 'mcp' httpResponse?: never mcpToolCallResponse: McpToolCallResponse } @@ -105,7 +105,7 @@ export function recordToolCallUsage({ tool?.name ?? null, // Whether this request was made via MCP or HTTP - requestMode, + edgeRequestMode, // IP address or session ID ip ?? sessionId, diff --git a/apps/gateway/src/lib/resolve-edge-request.ts b/apps/gateway/src/lib/resolve-edge-request.ts new file mode 100644 index 00000000..202482c9 --- /dev/null +++ b/apps/gateway/src/lib/resolve-edge-request.ts @@ -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 { + 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 + } +} diff --git a/apps/gateway/src/lib/resolve-http-edge-request.ts b/apps/gateway/src/lib/resolve-http-edge-request.ts index 192ee48f..5eaa747f 100644 --- a/apps/gateway/src/lib/resolve-http-edge-request.ts +++ b/apps/gateway/src/lib/resolve-http-edge-request.ts @@ -1,37 +1,22 @@ -import type { - AdminDeployment, - PricingPlan, - Tool -} from '@agentic/platform-types' import { assert } from '@agentic/platform-core' -import { parseToolIdentifier } from '@agentic/platform-validators' -import type { AdminConsumer, GatewayHonoContext, ToolCallArgs } from './types' -import { getAdminConsumer } from './get-admin-consumer' -import { getAdminDeployment } from './get-admin-deployment' +import type { + GatewayHonoContext, + ResolvedEdgeRequest, + ResolvedHttpEdgeRequest +} from './types' import { getTool } from './get-tool' import { getToolArgsFromRequest } from './get-tool-args-from-request' +import { resolveConsumerForEdgeRequest } from './resolve-edge-request' 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 - * billing subscription. - * - * Also ensures that the request is valid, enforces rate limits, and adds proxy- - * specific headers to the origin request. + * Resolves an input HTTP request to a specific deployment, tool call, consumer, + * and pricing plan. */ export async function resolveHttpEdgeRequest( - ctx: GatewayHonoContext + ctx: GatewayHonoContext, + resolvedEdgeRequest: ResolvedEdgeRequest ): Promise { const logger = ctx.get('logger') const ip = ctx.get('ip') @@ -40,15 +25,9 @@ export async function resolveHttpEdgeRequest( ? ctx.req.header('cache-control') : 'no-store' + const { deployment, parsedToolIdentifier } = resolvedEdgeRequest + const { toolName } = parsedToolIdentifier 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({ method, @@ -58,66 +37,36 @@ export async function resolveHttpEdgeRequest( logger.debug('request', { method, - pathname, deploymentIdentifier: deployment.identifier, toolName, tool }) - let pricingPlan: PricingPlan | undefined - let consumer: AdminConsumer | undefined - - const token = (ctx.req.header('authorization') || '') + const apiKey = (ctx.req.header('authorization') || '') .replace(/^Bearer /i, '') .trim() - if (token) { - consumer = await getAdminConsumer(ctx, token) - assert(consumer, 401, `Invalid auth token "${token}"`) - 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}"` - // ) + const { consumer, pricingPlan } = await resolveConsumerForEdgeRequest(ctx, { + deployment, + apiKey + }) + if (consumer) { if (!ctx.get('sessionId')) { ctx.set('sessionId', `${consumer.id}:${deployment.id}`) } } else { - // For unauthenticated requests, default to a free pricing plan if available. - pricingPlan = deployment.pricingPlans.find((plan) => plan.slug === 'free') - if (!ctx.get('sessionId')) { assert(ip, 500, 'IP address is required for unauthenticated requests') ctx.set('sessionId', `${ip}:${deployment.projectId}`) } } - assert(ctx.get('sessionId'), 500, 'Internal error: sessionId should be set') - - // Parse tool call args from the request body. + // Parse tool call arguments from the request body. const toolCallArgs = await getToolArgsFromRequest(ctx, { tool, deployment }) return { - deployment, + ...resolvedEdgeRequest, consumer, pricingPlan, tool, diff --git a/apps/gateway/src/lib/resolve-mcp-edge-request.ts b/apps/gateway/src/lib/resolve-mcp-edge-request.ts index 958da975..45a0e92b 100644 --- a/apps/gateway/src/lib/resolve-mcp-edge-request.ts +++ b/apps/gateway/src/lib/resolve-mcp-edge-request.ts @@ -1,71 +1,27 @@ -import type { AdminDeployment, PricingPlan } from '@agentic/platform-types' -import { assert } from '@agentic/platform-core' -import { parseToolIdentifier } from '@agentic/platform-validators' - -import type { AdminConsumer, GatewayHonoContext } from './types' -import { getAdminConsumer } from './get-admin-consumer' -import { getAdminDeployment } from './get-admin-deployment' - -export type ResolvedMcpEdgeRequest = { - deployment: AdminDeployment - consumer?: AdminConsumer - pricingPlan?: PricingPlan - ip?: string -} +import type { + GatewayHonoContext, + ResolvedEdgeRequest, + ResolvedMcpEdgeRequest +} from './types' +import { resolveConsumerForEdgeRequest } from './resolve-edge-request' export async function resolveMcpEdgeRequest( - ctx: GatewayHonoContext + ctx: GatewayHonoContext, + resolvedEdgeRequest: ResolvedEdgeRequest ): Promise { - const requestUrl = new URL(ctx.req.url) - const { pathname } = requestUrl - const requestedDeploymentIdentifier = pathname - .replace(/^\//, '') - .replace(/\/$/, '') - const { deploymentIdentifier } = parseToolIdentifier( - requestedDeploymentIdentifier - ) - - const deployment = await getAdminDeployment(ctx, deploymentIdentifier) + const { deployment } = resolvedEdgeRequest + // TODO: Should MCP edge requests also support Authorization header? const apiKey = ctx.req.query('apiKey')?.trim() - let consumer: AdminConsumer | undefined - let pricingPlan: PricingPlan | undefined - if (apiKey) { - consumer = await getAdminConsumer(ctx, apiKey) - assert(consumer, 401, `Invalid api key "${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') - } + const { consumer, pricingPlan } = await resolveConsumerForEdgeRequest(ctx, { + deployment, + apiKey + }) return { - deployment, + ...resolvedEdgeRequest, consumer, - pricingPlan, - ip: ctx.get('ip') + pricingPlan } } diff --git a/apps/gateway/src/lib/resolve-origin-tool-call.ts b/apps/gateway/src/lib/resolve-origin-tool-call.ts index 88aa2aa0..42730c30 100644 --- a/apps/gateway/src/lib/resolve-origin-tool-call.ts +++ b/apps/gateway/src/lib/resolve-origin-tool-call.ts @@ -123,7 +123,7 @@ export async function resolveOriginToolCall({ const pricingPlanToolOverride = pricingPlan ? toolConfig.pricingPlanOverridesMap?.[pricingPlan.slug] : undefined - const isToolConfigEnabled = toolConfig.enabled ?? true + const isToolEnabled = toolConfig.enabled ?? true // Check if this tool is configured for pricing-plan-specific overrides // which take precedence over the tool's default behavior. @@ -131,11 +131,11 @@ export async function resolveOriginToolCall({ if (pricingPlanToolOverride.enabled !== undefined) { assert( pricingPlanToolOverride.enabled, - isToolConfigEnabled ? 403 : 404, + isToolEnabled ? 403 : 404, `Tool "${tool.name}" is disabled for pricing plan "${pricingPlan.slug}"` ) } else { - assert(isToolConfigEnabled, 404, `Tool "${tool.name}" is disabled`) + assert(isToolEnabled, 404, `Tool "${tool.name}" is disabled`) } if (pricingPlanToolOverride.reportUsage !== undefined) { @@ -146,7 +146,7 @@ export async function resolveOriginToolCall({ rateLimit = pricingPlanToolOverride.rateLimit } } else { - assert(isToolConfigEnabled, 404, `Tool "${tool.name}" is disabled`) + assert(isToolEnabled, 404, `Tool "${tool.name}" is disabled`) } } else { if (cacheControl) { diff --git a/apps/gateway/src/lib/types.ts b/apps/gateway/src/lib/types.ts index 2bc77434..37e5d514 100644 --- a/apps/gateway/src/lib/types.ts +++ b/apps/gateway/src/lib/types.ts @@ -6,10 +6,14 @@ import type { } from '@agentic/platform-hono' import type { AdminConsumer as AdminConsumerImpl, + AdminDeployment, + PricingPlan, RateLimit, + Tool, ToolConfig, User } from '@agentic/platform-types' +import type { ParsedToolIdentifier } from '@agentic/platform-validators' import type { Client as McpClient } from '@modelcontextprotocol/sdk/client/index.js' import type { Context } from 'hono' import type { Simplify } from 'type-fest' @@ -57,10 +61,30 @@ export type RateLimitState = { export type RateLimitCache = Map export type CacheStatus = 'HIT' | 'MISS' | 'BYPASS' | 'DYNAMIC' -export type RequestMode = 'mcp' | 'http' +export type EdgeRequestMode = 'mcp' | 'http' export type WaitUntil = (promise: Promise) => void +export interface ResolvedEdgeRequest extends Record { + 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 = { toolCallArgs: ToolCallArgs originRequest?: Request diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 288d22f7..b01bf5c6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -463,6 +463,9 @@ importers: '@agentic/platform-validators': specifier: workspace:* version: link:../../packages/validators + '@cloudflare/workers-oauth-provider': + specifier: ^0.0.5 + version: 0.0.5 '@hono/zod-validator': specifier: 'catalog:' version: 0.7.0(hono@4.7.11)(zod@3.25.62) @@ -969,6 +972,9 @@ packages: cpu: [x64] os: [win32] + '@cloudflare/workers-oauth-provider@0.0.5': + resolution: {integrity: sha512-t1x5KAzsubCvb4APnJ93z407X1x7SGj/ga5ziRnwIb/iLy4PMkT/hgd1y5z7Bbsdy5Fy6mywhCP4lym24bX66w==} + '@cloudflare/workers-types@4.20250610.0': resolution: {integrity: sha512-HxnUoey3QxCEfy07pUm7J42jBi9YPHq/hA3fw6JmOqYLHdviHI28OA8lup+2RUaHwDzh6q1DSfrBvvDqde645A==} @@ -5986,6 +5992,10 @@ snapshots: '@cloudflare/workerd-windows-64@1.20250604.0': optional: true + '@cloudflare/workers-oauth-provider@0.0.5': + dependencies: + '@cloudflare/workers-types': 4.20250610.0 + '@cloudflare/workers-types@4.20250610.0': {} '@commander-js/extra-typings@14.0.0(commander@14.0.0)':