diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/fixtures/sdk-root-exports.json b/apps/e2e/demo-e2e-bundle-size/e2e/fixtures/sdk-root-exports.json new file mode 100644 index 000000000..9ece5e7ab --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/fixtures/sdk-root-exports.json @@ -0,0 +1,1044 @@ +{ + "ANSWER_COUNTERS": "object", + "Adapter": "function", + "AdapterEntry": "class", + "AdapterKind": "object", + "Agent": "function", + "AgentCallDepthExceededError": "class", + "AgentCallHook": "object", + "AgentConfigKeyNotFoundError": "class", + "AgentConfigurationError": "class", + "AgentContext": "class", + "AgentEntry": "class", + "AgentExecutionError": "class", + "AgentKind": "object", + "AgentLlmError": "class", + "AgentLoopExceededError": "class", + "AgentMethodNotAvailableError": "class", + "AgentNotConfiguredError": "class", + "AgentNotFoundError": "class", + "AgentTimeoutError": "class", + "AgentToolExecutionError": "class", + "AgentToolNotFoundError": "class", + "AgentVisibilityError": "class", + "AnthropicAdapter": "class", + "App": "function", + "AppEntry": "class", + "AppKind": "object", + "AroundHookOf": "function", + "AsyncAuthProvider": "function", + "AsyncProvider": "function", + "AudioOutputSchema": "object", + "Auth": "class", + "AuthConfigurationError": "class", + "AuthHookStage": "object", + "AuthProvider": "function", + "AuthProviderEntry": "class", + "AuthProviderKind": "object", + "AuthProviderScope": "object", + "AuthProvidersNotConfiguredError": "class", + "AuthorizationRequiredError": "class", + "BUDGET_LIST_POLICIES": "array", + "BUDGET_META_KEY": "string", + "BaseConfig": "class", + "BaseEntry": "class", + "CallRecorderConfigurationError": "class", + "CallRecorderPlugin": "class", + "CancelJobTool": "class", + "CapabilityCache": "class", + "Channel": "function", + "ChannelContext": "class", + "ChannelEmitter": "class", + "ChannelEntry": "class", + "ChannelEventBus": "class", + "ChannelInstance": "class", + "ChannelKind": "object", + "ChannelListHook": "object", + "ChannelNotificationService": "class", + "ChannelRegistry": "class", + "ChannelReplyTool": "class", + "ChannelSendHook": "object", + "CircuitBreaker": "class", + "CircuitBreakerManager": "class", + "CircuitOpenError": "class", + "CliTaskRunner": "class", + "CompletionHook": "object", + "ConcurrencyLimitError": "class", + "ConfigMissingError": "class", + "ConfigNotFoundError": "class", + "ConfigPlugin": "class", + "ConfigPluginConfigToken": "symbol", + "ConfigService": "class", + "ConfigValidationError": "class", + "Context": "class", + "ContextExtensionNotAvailableError": "class", + "CredentialProviderAlreadyRegisteredError": "class", + "CrossOriginRequestError": "class", + "DEFAULT_ELICITATION_OPTIONS": "object", + "DEFAULT_ELICIT_TTL": "number", + "DEFAULT_FALLBACK_WAIT_TTL": "number", + "DEFAULT_HEALTH_OPTIONS": "object", + "DEFAULT_INSTRUCTIONS_BUDGET": "object", + "DEFAULT_METRICS_OPTIONS": "object", + "DEFAULT_NPM_REGISTRY": "string", + "DEFAULT_OUTPUT_SCHEMA_MODE": "string", + "DEFAULT_QUESTION_TTL_MS": "number", + "DEFAULT_REGISTRY_PUBLIC_KEY_ENV": "string", + "DEFAULT_SCHEMA_DESCRIPTION_FORMAT": "string", + "DEFAULT_TOOL_PAGINATION": "object", + "DependencyCycleError": "class", + "DependencyNotFoundError": "class", + "DidHookOf": "function", + "DistributedSemaphore": "class", + "DuplicateAppIdError": "class", + "Duty": "function", + "DutyNameConflictError": "class", + "DynamicAdapter": "class", + "DynamicAdapterNameError": "class", + "DynamicJobDirectExecutionError": "class", + "DynamicJobRegistrationDisabledError": "class", + "DynamicPlugin": "class", + "ELICITATION_FALLBACK_JSON_SCHEMA": "object", + "ESM_SH_BASE_URL": "string", + "EXT_APPS_ERROR_CODES": "object", + "ElicitationDisabledError": "class", + "ElicitationEncryptionError": "class", + "ElicitationFallbackRequired": "class", + "ElicitationNotOwnedError": "class", + "ElicitationNotSupportedError": "class", + "ElicitationSecretRequiredError": "class", + "ElicitationStoreNotInitializedError": "class", + "ElicitationSubscriptionError": "class", + "ElicitationTimeoutError": "class", + "EnclaveExecutionError": "class", + "EncryptedElicitationStore": "class", + "EncryptionContextNotSetError": "class", + "EncryptionKeyNotConfiguredError": "class", + "EntryUnavailableError": "class", + "EntryValidationError": "class", + "ErrorHandler": "class", + "EsmCacheError": "class", + "EsmCacheManager": "class", + "EsmInvalidSpecifierError": "class", + "EsmManifestInvalidError": "class", + "EsmModuleLoader": "class", + "EsmPackageLoadError": "class", + "EsmRegistryAuthError": "class", + "EsmVersionResolutionError": "class", + "ExecuteJobTool": "class", + "ExecuteWorkflowTool": "class", + "ExecutionContextBase": "class", + "ExecutionTimeoutError": "class", + "ExtAppsError": "class", + "ExtAppsInvalidParamsError": "class", + "ExtAppsMessageHandler": "class", + "ExtAppsMethodNotFoundError": "class", + "ExtAppsNotSupportedError": "class", + "ExtAppsToolNotFoundError": "class", + "ExternalEntryError": "class", + "ExternalEntryLoadError": "class", + "ExternalEntryNotFoundError": "class", + "ExternalEntryNotSupportedError": "class", + "ExternalSkillProviderBase": "class", + "FRONTMCP_CONFIG_METADATA_KEY": "string", + "FRONTMCP_CONTEXT": "symbol", + "FRONTMCP_LOGGER": "symbol", + "FRONTMCP_SUPPORTED_PROTOCOL_VERSIONS": "array", + "FeatureOffError": "class", + "FinallyHookOf": "function", + "Flow": "function", + "FlowBase": "class", + "FlowControl": "class", + "FlowEntry": "class", + "FlowExitedWithoutOutputError": "class", + "FlowHooksOf": "function", + "FlowInputMissingError": "class", + "FlowKind": "object", + "FlowNotRegisteredError": "class", + "FrontMcp": "function", + "FrontMcpAdapter": "function", + "FrontMcpAdapterTokens": "object", + "FrontMcpAgent": "function", + "FrontMcpAgentTokens": "object", + "FrontMcpApp": "function", + "FrontMcpAuth": "class", + "FrontMcpAuthProvider": "function", + "FrontMcpAuthProviderTokens": "object", + "FrontMcpChannelTokens": "object", + "FrontMcpConfig": "symbol", + "FrontMcpContext": "class", + "FrontMcpContextProvider": "object", + "FrontMcpContextStorage": "class", + "FrontMcpFlow": "function", + "FrontMcpFlowHookTokens": "object", + "FrontMcpFlowTokens": "object", + "FrontMcpInstance": "class", + "FrontMcpJob": "function", + "FrontMcpJobTokens": "object", + "FrontMcpLocalAppTokens": "object", + "FrontMcpLogTransport": "function", + "FrontMcpLogTransportTokens": "object", + "FrontMcpLogger": "class", + "FrontMcpPlugin": "function", + "FrontMcpPluginTokens": "object", + "FrontMcpPrompt": "function", + "FrontMcpPromptTokens": "object", + "FrontMcpProvider": "function", + "FrontMcpProviderTokens": "object", + "FrontMcpRemoteAppTokens": "object", + "FrontMcpResource": "function", + "FrontMcpResourceTemplate": "function", + "FrontMcpResourceTemplateTokens": "object", + "FrontMcpResourceTokens": "object", + "FrontMcpServer": "class", + "FrontMcpSkill": "function", + "FrontMcpSkillTokens": "object", + "FrontMcpTokens": "object", + "FrontMcpTool": "function", + "FrontMcpToolTokens": "object", + "FrontMcpWorkflow": "function", + "FrontMcpWorkflowTokens": "object", + "GLOBAL_RATE_LIMIT_CHECKED": "symbol", + "GenericServerError": "class", + "GetJobStatusTool": "class", + "GetWorkflowStatusTool": "class", + "GlobalConfigNotFoundError": "class", + "GuardError": "class", + "GuardLimitMcpError": "class", + "GuardManager": "class", + "GuardStorageUnavailableError": "class", + "GuardStorageUnsupportedError": "class", + "HOOK_OBSERVER_KEY": "symbol", + "HOOK_TRACE_META_KEY": "string", + "HTTP_ROUTE_PATH_MESSAGE": "string", + "HaConfigurationError": "class", + "HealthCheckManager": "class", + "HealthChecker": "class", + "HookEntry": "class", + "HookKind": "object", + "HookOrderCycleError": "class", + "HookPhase": "object", + "HookTargetNotDefinedError": "class", + "HostServerAdapter": "class", + "HttpBinarySchema": "object", + "HttpConsumedSchema": "object", + "HttpCookieSchema": "object", + "HttpCookies": "object", + "HttpEmptySchema": "object", + "HttpEmptyStatus": "object", + "HttpHeaders": "object", + "HttpHook": "object", + "HttpHtmlSchema": "object", + "HttpImageSchema": "object", + "HttpJsonRpcSchema": "object", + "HttpJsonSchema": "object", + "HttpNextSchema": "object", + "HttpProblemSchema": "object", + "HttpRedirectSchema": "object", + "HttpRedirectStatus": "object", + "HttpRoute": "function", + "HttpRouteContext": "class", + "HttpRouteHook": "object", + "HttpSseSchema": "object", + "HttpStatus": "object", + "HttpStreamSchema": "object", + "HttpTextSchema": "object", + "HttpWebResponseSchema": "object", + "IP_FILTER_REJECTION_BODY": "object", + "ImageOutputSchema": "object", + "InMemoryOrchestratedTokenStore": "class", + "InMemoryStoreRequiredError": "class", + "InProcessTaskRunner": "class", + "InputRequiredSignal": "class", + "InternalMcpError": "class", + "InvalidDecoratorMetadataError": "class", + "InvalidDependencyScopeError": "class", + "InvalidEntityError": "class", + "InvalidFileSignalError": "class", + "InvalidHookFlowError": "class", + "InvalidHttpRouteError": "class", + "InvalidInputError": "class", + "InvalidInstructionSourceError": "class", + "InvalidMethodError": "class", + "InvalidOffloadCallError": "class", + "InvalidOutputError": "class", + "InvalidPeopleAuthOptionsError": "class", + "InvalidPluginScopeError": "class", + "InvalidQuestionStoreError": "class", + "InvalidRegistryKindError": "class", + "InvalidResourceStoreError": "class", + "InvalidResourceUriError": "class", + "InvalidRetryOptionsError": "class", + "InvalidSharedServerOptionsError": "class", + "InvalidSkillError": "class", + "InvalidTransportSessionError": "class", + "InvalidUseClassError": "class", + "InvalidUseFactoryError": "class", + "InvalidUseValueError": "class", + "InvokeStateMissingKeyError": "class", + "IpBlockedError": "class", + "IpFilter": "class", + "IpNotAllowedError": "class", + "JOB_WORKER_MARKER": "string", + "JSON_RPC": "string", + "Job": "function", + "JobAdmissionTimeoutError": "class", + "JobCancelledError": "class", + "JobContext": "class", + "JobEmitter": "class", + "JobEntry": "class", + "JobExecutionManager": "class", + "JobHook": "object", + "JobInstance": "class", + "JobInterruptedError": "class", + "JobKind": "object", + "JobNotAuthorizedError": "class", + "JobPermissionGuard": "class", + "JobRegistry": "class", + "JobRunFinishedError": "class", + "JobRunNotCancellableError": "class", + "JobRunNotFoundError": "class", + "JobRunResource": "class", + "JobRunnerBootError": "class", + "JobRunnerUnavailableError": "class", + "JobTimeoutError": "class", + "JwtSecretRequiredError": "class", + "JwtSecretWeakError": "class", + "LLM_ADAPTER": "symbol", + "LazyAppConfigurationError": "class", + "LazyAppLoadError": "class", + "LazyZodSchema": "class", + "ListJobsTool": "class", + "ListPromptsHook": "object", + "ListResourceTemplatesHook": "object", + "ListResourcesHook": "object", + "ListToolsHook": "object", + "ListWorkflowsTool": "class", + "LogLevel": "object", + "LogLevelName": "object", + "LogTransport": "function", + "LogTransportInterface": "class", + "LoggerEntry": "class", + "LoggerKind": "object", + "LoggerRegistry": "class", + "LoopbackTokenAuth": "class", + "MANIFEST_PRIMITIVE_KEYS": "array", + "MATERIALIZED_META_KEY": "string", + "MAX_CONCURRENT_TASKS_PER_SESSION": "number", + "MAX_JOB_TIMER_MS": "number", + "MCP_ERROR_CODES": "object", + "MIN_OUTPUT_BUDGET_BYTES": "number", + "MaterializedResourceContext": "class", + "McpClientService": "class", + "McpError": "class", + "McpStatelessClient": "class", + "McpStatelessClientAdapter": "class", + "McpStatelessError": "class", + "MemorySkillProvider": "class", + "MethodNotImplementedError": "class", + "MissingClientCapabilityError": "class", + "MissingPromptArgumentError": "class", + "MissingProvideError": "class", + "NEVER": "object", + "NO_BODY_STATUSES": "array", + "NO_STORE_HEADERS": "object", + "NameDisambiguationError": "class", + "NoProviderIdError": "class", + "OAuthAuthorizeHook": "object", + "OAuthCallbackHook": "object", + "OAuthRegisterHook": "object", + "OAuthTokenHook": "object", + "OUTPUT_SCHEMA_MODES": "array", + "OffloadAbortedError": "class", + "OffloadBudgetExceededError": "class", + "OffloadNotCloneableError": "class", + "OffloadUnsupportedRuntimeError": "class", + "OffloadWorkerError": "class", + "OpenAIAdapter": "class", + "OrchestratedAuthNotAvailableError": "class", + "OrchestratorJwksNotAvailableError": "class", + "PEOPLE_SESSION_COOKIE": "string", + "PLATFORM_CLIENT_INFO": "object", + "PRIMITIVE_FILTER_KEYS": "array", + "PROTOCOL_2026_07_28": "string", + "PROTOCOL_PRESETS": "object", + "PayloadTooLargeError": "class", + "PersonNotSignedInError": "class", + "Plugin": "function", + "PluginDependencyError": "class", + "PluginEntry": "class", + "PluginKind": "object", + "Prompt": "function", + "PromptContext": "class", + "PromptEntry": "class", + "PromptExecutionError": "class", + "PromptHook": "object", + "PromptKind": "object", + "PromptNotFoundError": "class", + "Provider": "function", + "ProviderBootstrapError": "class", + "ProviderConstructionError": "class", + "ProviderDependencyError": "class", + "ProviderEntry": "class", + "ProviderKind": "object", + "ProviderNotAvailableError": "class", + "ProviderNotInstantiatedError": "class", + "ProviderNotRegisteredError": "class", + "ProviderRegistry": "class", + "ProviderScope": "object", + "ProviderScopeMismatchError": "class", + "ProviderScopedAccessError": "class", + "PublicAccessDeniedError": "class", + "PublicMcpError": "class", + "QUESTIONS_PATH": "string", + "QUESTIONS_TOPIC": "string", + "QUESTION_INBOX_PATH": "string", + "QuestionAlreadyAnsweredError": "class", + "QuestionNotFoundError": "class", + "QuestionPendingError": "class", + "QuestionStore": "class", + "QuestionStoreBusyError": "class", + "QuestionSupersededError": "class", + "QueueTimeoutError": "class", + "QuotaExceededError": "class", + "REDIRECTS": "array", + "REGISTRY_SERVER_NAME_PATTERN": "object", + "RELATED_TASK_META_KEY": "string", + "RUN_TASK_ENV_VAR": "string", + "RUN_TASK_SUBCOMMAND": "string", + "RateLimitError": "class", + "RegisterJobTool": "class", + "RegisterWorkflowTool": "class", + "RegistryDefinitionNotFoundError": "class", + "RegistryDependencyNotRegisteredError": "class", + "RegistryGraphEntryNotFoundError": "class", + "RegistryNotInitializedError": "class", + "RemoteAuthError": "class", + "RemoteAuthorizationError": "class", + "RemoteCapabilityDiscoveryError": "class", + "RemoteCapabilityNotSupportedError": "class", + "RemoteConfigurationError": "class", + "RemoteConnectionError": "class", + "RemoteDisconnectError": "class", + "RemoteNotConnectedError": "class", + "RemotePromptGetError": "class", + "RemotePromptNotFoundError": "class", + "RemoteResourceNotFoundError": "class", + "RemoteResourceReadError": "class", + "RemoteTimeoutError": "class", + "RemoteToolExecutionError": "class", + "RemoteToolNotFoundError": "class", + "RemoteTransportError": "class", + "RemoteUnsupportedTransportError": "class", + "RemoveJobTool": "class", + "RemoveWorkflowTool": "class", + "RequestContextNotAvailableError": "class", + "RequiredConfigUndefinedError": "class", + "ReservedAppIdError": "class", + "Resource": "function", + "ResourceContext": "class", + "ResourceEntry": "class", + "ResourceHook": "object", + "ResourceKind": "object", + "ResourceLinkOutputSchema": "object", + "ResourceNotFoundError": "class", + "ResourceOutputSchema": "object", + "ResourceReadError": "class", + "ResourceStore": "class", + "ResourceTemplate": "function", + "ResourceTemplateKind": "object", + "SCHEMA_DESCRIPTION_FORMATS": "array", + "SERVER_MANIFEST_FILE": "string", + "SERVER_MANIFEST_VERSION": "number", + "STATELESS_SESSION_ID": "string", + "SURFACE_META_KEY": "string", + "SamplingNotAvailableError": "class", + "ScopeConfigurationError": "class", + "ScopeDeniedError": "class", + "ScopeEntry": "class", + "ScopeKind": "object", + "ServerNotFoundError": "class", + "ServerRequest": "class", + "ServerRequestTokens": "object", + "ServerResponse": "class", + "ServerlessHandlerNotInitializedError": "class", + "SessionClaimConflictError": "class", + "SessionHookStage": "object", + "SessionIdEmptyError": "class", + "SessionIdentityRequiredError": "class", + "SessionMissingError": "class", + "SessionOwnerUnreachableError": "class", + "SessionSecretRequiredError": "class", + "SessionVerificationFailedError": "class", + "SharedServerIdentityError": "class", + "SharedServerStartError": "class", + "SignInLockedError": "class", + "SignInRefusedError": "class", + "Skill": "function", + "SkillContext": "class", + "SkillContextNotImplementedError": "class", + "SkillEmitter": "class", + "SkillEntry": "class", + "SkillHttpAuthValidator": "class", + "SkillInstance": "class", + "SkillInstructionFetchError": "class", + "SkillKind": "object", + "SkillRegistry": "class", + "SkillSessionError": "class", + "SkillToolValidator": "class", + "SkillValidationError": "class", + "SlidingWindowRateLimiter": "class", + "SocketInUseError": "class", + "SocketPathConflictError": "class", + "SocketPermissionError": "class", + "SqliteStorageToken": "symbol", + "StageHookOf": "function", + "StaleToolListError": "class", + "StorageElicitationStore": "class", + "StorageTaskStore": "class", + "TASKS_CLIENT_CAPABILITY": "object", + "TASKS_EXTENSION_ID": "string", + "TASK_DEFAULTS": "object", + "TASK_WORKER_MARKER": "string", + "TASK_WORKER_MODE_FLAG": "string", + "TaskAlreadyTerminalError": "class", + "TaskAugmentationNotSupportedError": "class", + "TaskAugmentationRequiredError": "class", + "TaskConcurrencyLimitError": "class", + "TaskNotFoundError": "class", + "TaskNotifier": "class", + "TaskRegistry": "class", + "TaskStoreNotInitializedError": "class", + "TaskStoreNotSupportedError": "class", + "TasksCancelFlow": "class", + "TasksGetFlow": "class", + "TasksListFlow": "class", + "TasksResultFlow": "class", + "TokenLeakDetectedError": "class", + "TokenNotAvailableError": "class", + "TokenStoreRequiredError": "class", + "Tool": "function", + "ToolAliasConflictError": "class", + "ToolCallError": "class", + "ToolContext": "class", + "ToolCredentialsRequiredError": "class", + "ToolEntry": "class", + "ToolExecutionError": "class", + "ToolHook": "object", + "ToolHookStage": "object", + "ToolInstance": "class", + "ToolKind": "object", + "ToolNameConflictError": "class", + "ToolNotConsentedError": "class", + "ToolNotFoundError": "class", + "ToolRegistry": "class", + "TransportAlreadyStartedError": "class", + "TransportBusRequiredError": "class", + "TransportNotConnectedError": "class", + "TransportServiceNotAvailableError": "class", + "UNDECLARED_KEYS_POLICIES": "array", + "UnauthorizedError": "class", + "UnenforcedMetadataError": "class", + "UnsafeDutyStoreError": "class", + "UnsafeSharedServerDirectoryError": "class", + "UnsafeSocketDirectoryError": "class", + "UnsupportedClientVersionError": "class", + "UnsupportedContentTypeError": "class", + "UnsupportedHookOwnerKindError": "class", + "UnsupportedTransportTypeError": "class", + "VaultLoadError": "class", + "VaultNotFoundError": "class", + "VercelKvAsyncInitRequiredError": "class", + "VercelKvNotSupportedError": "class", + "VersionPoller": "class", + "VersionResolver": "class", + "WillHookOf": "function", + "Workflow": "function", + "WorkflowContext": "class", + "WorkflowDagValidationError": "class", + "WorkflowEmitter": "class", + "WorkflowEngine": "class", + "WorkflowEntry": "class", + "WorkflowInstance": "class", + "WorkflowJobTimeoutError": "class", + "WorkflowKind": "object", + "WorkflowRegistry": "class", + "WorkflowStepExecutor": "class", + "WorkflowStepNotFoundError": "class", + "WorkflowTimeoutError": "class", + "ZodAny": "function", + "ZodArray": "function", + "ZodBigInt": "function", + "ZodBoolean": "function", + "ZodDate": "function", + "ZodDefault": "function", + "ZodDiscriminatedUnion": "function", + "ZodEnum": "function", + "ZodError": "function", + "ZodIntersection": "function", + "ZodLiteral": "function", + "ZodNever": "function", + "ZodNull": "function", + "ZodNullable": "function", + "ZodNumber": "function", + "ZodObject": "function", + "ZodOptional": "function", + "ZodRecord": "function", + "ZodString": "function", + "ZodSymbol": "function", + "ZodTuple": "function", + "ZodType": "function", + "ZodUndefined": "function", + "ZodUnion": "function", + "ZodUnknown": "function", + "ZodVoid": "function", + "acquireConcurrencySlots": "function", + "adapterInitOptionsOf": "function", + "addHookObserver": "function", + "agent": "function", + "aiPlatformTypeSchema": "object", + "allowsPublicAccess": "function", + "annotatedFrontMcpAdaptersSchema": "object", + "annotatedFrontMcpAgentsSchema": "object", + "annotatedFrontMcpAppSchema": "object", + "annotatedFrontMcpAuthProvidersSchema": "object", + "annotatedFrontMcpChannelsSchema": "object", + "annotatedFrontMcpJobsSchema": "object", + "annotatedFrontMcpLoggerSchema": "object", + "annotatedFrontMcpPluginsSchema": "object", + "annotatedFrontMcpPromptsSchema": "object", + "annotatedFrontMcpProvidersSchema": "object", + "annotatedFrontMcpResourcesSchema": "object", + "annotatedFrontMcpSkillsSchema": "object", + "annotatedFrontMcpToolsSchema": "object", + "annotatedFrontMcpWorkflowsSchema": "object", + "app": "function", + "appAuthOptionsSchema": "object", + "appFilterConfigSchema": "object", + "applyPrimitiveFilter": "function", + "assertStaticStartupConfig": "function", + "authInfoFromAuthorization": "function", + "authOptionsSchema": "object", + "authenticateFnSchema": "object", + "authorizationRequiredDataSchema": "object", + "authorizationRequiredMetaSchema": "object", + "authorizationRequiredParamsSchema": "object", + "authorizationSchema": "object", + "authorizeSessionTermination": "function", + "authorizeSkillHttpRequest": "function", + "buildChannelInstructions": "function", + "buildEsmPromptRecord": "function", + "buildEsmResourceRecord": "function", + "buildEsmShUrl": "function", + "buildEsmToolRecord": "function", + "buildManifest": "function", + "buildParamHeaders": "function", + "buildPartitionContext": "function", + "buildPersistentWebStandardMcp": "function", + "buildRemotePromptRecord": "function", + "buildRemoteResourceRecord": "function", + "buildRemoteResourceTemplateRecord": "function", + "buildRemoteToolRecord": "function", + "buildSkillsCatalogSummary": "function", + "buildStorageKey": "function", + "canDeliverNotifications": "function", + "captureCallerDir": "function", + "captureCallerFile": "function", + "challengeHeaders": "function", + "channel": "function", + "channelNotificationSchema": "object", + "channelSourceConfigSchema": "object", + "channelsConfigSchema": "object", + "clearCreateCache": "function", + "clientProfilesOptionsSchema": "object", + "compareVersions": "function", + "composeCallerInstructions": "function", + "composeInitializeInstructions": "function", + "computeIssuer": "function", + "computeResource": "function", + "computeTaskCapabilities": "function", + "concurrencyConfigSchema": "object", + "connect": "function", + "connectClaude": "function", + "connectLangChain": "function", + "connectOpenAI": "function", + "connectVercelAI": "function", + "consentConfigSchema": "object", + "consumePendingTC39Hooks": "function", + "create": "function", + "createChildSpanContext": "function", + "createContainer": "function", + "createContextResolver": "function", + "createDeferredServerBuild": "function", + "createDirectResolver": "function", + "createElicitationStore": "function", + "createElicitationStoreFromStorage": "function", + "createErrorHandler": "function", + "createEsmPromptContextClass": "function", + "createEsmPromptInstance": "function", + "createEsmResourceContextClass": "function", + "createEsmResourceInstance": "function", + "createEsmToolContextClass": "function", + "createEsmToolInstance": "function", + "createExtAppsMessageHandler": "function", + "createGuardManager": "function", + "createInMemoryServer": "function", + "createMemoryElicitationStore": "function", + "createMemorySkillProvider": "function", + "createMemoryTaskStore": "function", + "createQuestionStore": "function", + "createRemotePromptContextClass": "function", + "createRemotePromptInstance": "function", + "createRemoteResourceContextClass": "function", + "createRemoteResourceInstance": "function", + "createRemoteResourceTemplateInstance": "function", + "createRemoteToolContextClass": "function", + "createRemoteToolInstance": "function", + "createSkillHttpAuthValidator": "function", + "createSkillInstance": "function", + "createSkillStorageProvider": "function", + "createTaskStore": "function", + "createWebFetchHandler": "function", + "decideIntent": "function", + "decisionSchema": "object", + "decryptElicitationData": "function", + "deepMerge": "function", + "default": "object", + "defaultHttpPort": "function", + "defineDutyMetadata": "function", + "defineHttpRouteMetadata": "function", + "deriveElicitationKey": "function", + "describeConfigIssues": "function", + "deserializeAndDecrypt": "function", + "deserializeElicitationBlob": "function", + "detectConflicts": "function", + "detectPlatform": "function", + "devtoolsOptionsSchema": "object", + "dutyMetadataOf": "function", + "dutyMetadataSchema": "object", + "eagerZ": "object", + "elicitationOptionsSchema": "object", + "enabledOptionSchema": "object", + "encryptAndSerialize": "function", + "encryptElicitationData": "function", + "enforceGlobalRateLimit": "function", + "enforceIpFilter": "function", + "esmRegistryAuthSchema": "object", + "eventStoreConfigSchema": "object", + "expandProtocolConfig": "function", + "extAppsHostCapabilitiesSchema": "object", + "extAppsOptionsSchema": "object", + "extendOutputSchemaForElicitation": "function", + "extendedAgentMetadata": "symbol", + "extendedJobMetadata": "symbol", + "extendedPromptMetadata": "symbol", + "extendedResourceMetadata": "symbol", + "extendedResourceTemplateMetadata": "symbol", + "extendedSkillMetadata": "symbol", + "extendedToolMetadata": "symbol", + "extendedWorkflowMetadata": "symbol", + "extractPublicMessage": "function", + "extractSchemaPaths": "function", + "extractToolNames": "function", + "federatedAuthConfigSchema": "object", + "fileSignal": "function", + "filterServableSkills": "function", + "findBudgetOverruns": "function", + "firstCallerFile": "function", + "fitResourceAnswer": "function", + "fitToolAnswer": "function", + "flatRemoteProviderFields": "object", + "forceMaterialize": "function", + "formatMcpErrorResponse": "function", + "formatOutputSchemaForDescription": "function", + "formatResultForPlatform": "function", + "formatSkillForLLM": "function", + "formatToolsForPlatform": "function", + "frontMcpAdapterMetadataSchema": "object", + "frontMcpAgent": "function", + "frontMcpAgentMetadataSchema": "object", + "frontMcpApp": "function", + "frontMcpAuthProviderMetadataSchema": "object", + "frontMcpBaseSchema": "object", + "frontMcpChannelMetadataSchema": "object", + "frontMcpFlowMetadataSchema": "object", + "frontMcpJob": "function", + "frontMcpJobMetadataSchema": "object", + "frontMcpLocalAppMetadataSchema": "object", + "frontMcpLogTransportMetadataSchema": "object", + "frontMcpMetadataSchema": "object", + "frontMcpPackageManifestSchema": "object", + "frontMcpPluginMetadataSchema": "object", + "frontMcpPrompt": "function", + "frontMcpPromptMetadataSchema": "object", + "frontMcpProviderMetadataSchema": "object", + "frontMcpRemoteAppMetadataSchema": "object", + "frontMcpResource": "function", + "frontMcpResourceMetadataSchema": "object", + "frontMcpResourceTemplate": "function", + "frontMcpResourceTemplateMetadataSchema": "object", + "frontMcpSkill": "function", + "frontMcpTool": "function", + "frontMcpToolMetadataSchema": "object", + "frontMcpWorkflow": "function", + "frontMcpWorkflowMetadataSchema": "object", + "generateElicitationId": "function", + "generateEnvFallbacks": "function", + "generateFallbacks": "function", + "generateTaskId": "function", + "generateTraceContext": "function", + "getCallSurface": "function", + "getConfig": "function", + "getDecoratorConfig": "function", + "getElicitationSecret": "function", + "getGlobalStoreConfig": "function", + "getNestedValue": "function", + "getPinnedPublicUrl": "function", + "getRegistryUrl": "function", + "getRequestBaseUrl": "function", + "getRunningTool": "function", + "getServerlessHandler": "function", + "getServerlessHandlerAsync": "function", + "getSkillMetadata": "function", + "getToolNameFromClass": "function", + "guardConfigSchema": "object", + "handleWaitingFallback": "function", + "hasSkillAuditFactory": "function", + "healthOptionsSchema": "object", + "healthReadyzOptionsSchema": "object", + "hookOptionsSchema": "object", + "httpInputSchema": "object", + "httpOptionsSchema": "object", + "httpOutputSchema": "object", + "httpRequestInputSchema": "object", + "httpRespond": "object", + "httpRouteMetadataOf": "function", + "incrementalAuthConfigSchema": "object", + "initOptionsOf": "function", + "instructionsBudgetOptionsSchema": "object", + "intentSchema": "object", + "ipFilterConfigSchema": "object", + "isAlive": "function", + "isAnonymousSubject": "function", + "isAuthError": "function", + "isChannelClass": "function", + "isChannelFunction": "function", + "isClientFacingError": "function", + "isConnectionError": "function", + "isDistributedMode": "function", + "isDynamicPluginClass": "function", + "isElicitationEncryptionAvailable": "function", + "isEnabledSource": "function", + "isEncryptedBlob": "function", + "isEntryGatedBy": "function", + "isFileInstructions": "function", + "isHttpRoutePath": "function", + "isInlineInstructions": "function", + "isLazy": "function", + "isLocalMode": "function", + "isMrtrSignal": "function", + "isNewerVersion": "function", + "isOrchestratedLocal": "function", + "isOrchestratedMode": "function", + "isOrchestratedRemote": "function", + "isPackageSpecifier": "function", + "isPrimitiveIncluded": "function", + "isProxyTrusted": "function", + "isPublicError": "function", + "isPublicMode": "function", + "isPublicUrlPinned": "function", + "isPubsubConfigured": "function", + "isRedisProvider": "function", + "isRemoteMode": "function", + "isResourceKey": "function", + "isSkillDecorated": "function", + "isSkillRecord": "function", + "isStaticMode": "function", + "isTaskWorkerProcess": "function", + "isTerminal": "function", + "isToolRefWithClass": "function", + "isToolRefWithName": "function", + "isTransientError": "function", + "isTransparentMode": "function", + "isUrlInstructions": "function", + "isValidRange": "function", + "isValidVersion": "function", + "isVercelKvProvider": "function", + "job": "function", + "jobRunnerSchema": "object", + "joinPath": "function", + "jsonWebKeySetSchema": "object", + "jwkParametersSchema": "object", + "jwkSchema": "object", + "lazyAppMetadataSchema": "object", + "lazyZ": "function", + "lifecycleOptionsSchema": "object", + "lintServerManifest": "function", + "loadConfig": "function", + "loadEnvFiles": "function", + "loadSkillDirectory": "function", + "localAuthSchema": "object", + "localDcrClientSchema": "object", + "localDcrConfigSchema": "object", + "localSigningConfigSchema": "object", + "loggingOptionsSchema": "object", + "loginConfigSchema": "object", + "makeWellKnownPaths": "function", + "mapEnvToNestedConfig": "function", + "maxSatisfying": "function", + "mcpRegistryOptionsSchema": "object", + "mcpRegistryRepositorySchema": "object", + "mcpSessionHeaderSchema": "object", + "metricsOptionsSchema": "object", + "metricsProcessOptionsSchema": "object", + "negotiateRemoteProtocol": "function", + "normalizeEndpointConfig": "function", + "normalizeEntryPrefix": "function", + "normalizeEsmExport": "function", + "normalizeNameForEnv": "function", + "normalizePathSegment": "function", + "normalizeResourceUri": "function", + "normalizeScopeBase": "function", + "normalizeSkill": "function", + "normalizeSkillsConfigOptions": "function", + "normalizeToolAuthProviders": "function", + "normalizeToolRef": "function", + "observabilityOptionsSchema": "object", + "offloadOptionsSchema": "object", + "orchestratedLocalSchema": "object", + "orchestratedRemoteSchema": "object", + "outboundFetchOptionsSchema": "object", + "outputPolicySchema": "object", + "packageLoaderSchema": "object", + "paginationOptionsSchema": "object", + "parseAuthOptions": "function", + "parseCallerDir": "function", + "parseEnvContent": "function", + "parseEnvContentSync": "function", + "parseFrontMcpConfigLite": "function", + "parseListInput": "function", + "parsePackageSpecifier": "function", + "parseRedisUrl": "function", + "parseTraceContext": "function", + "partitionKeySchema": "object", + "partitionsByIdentity": "function", + "pathToEnvKey": "function", + "peekPendingTC39HooksForClass": "function", + "performElicit": "function", + "performListRoots": "function", + "performSample": "function", + "persistenceConfigSchema": "object", + "platformDetectionConfigSchema": "object", + "platformMappingEntrySchema": "object", + "populateProcessEnv": "function", + "previewItems": "function", + "printQuestionInboxLink": "function", + "prompt": "function", + "providerConfigSchema": "object", + "publicAccessConfigSchema": "object", + "publicAuthOptionsSchema": "object", + "pubsubOptionsSchema": "object", + "questionInboxRoutes": "function", + "questionSubjectOf": "function", + "rateLimitConfigSchema": "object", + "redisOptionsSchema": "object", + "redisProviderSchema": "object", + "redisUrlSchema": "object", + "refused": "function", + "registerOptionalModule": "function", + "registerPendingTC39Hook": "function", + "remoteAuthSchema": "object", + "remoteProviderConfigSchema": "object", + "resolveDefaultSqlitePath": "function", + "resolveEntryPath": "function", + "resolveInstructionsBudget": "function", + "resolveNameConflicts": "function", + "resolveOutputBudget": "function", + "resolveOutputSchemaMode": "function", + "resolvePartitionKey": "function", + "resolvePendingTC39HooksForClass": "function", + "resolvePrincipal": "function", + "resolveQuestionStorage": "function", + "resolveRegistryToken": "function", + "resolveSchemaDescriptionFormat": "function", + "resolveToolVisibility": "function", + "resolveUndeclaredKeysPolicy": "function", + "resolveWithFallbacks": "function", + "resolvesToVercelKvTaskBackend": "function", + "resource": "function", + "resourceKeySchema": "object", + "resourceStore": "function", + "resourceTemplate": "function", + "resourceUriMatches": "function", + "runHttpRequestFlowWeb": "function", + "runTaskInBackground": "function", + "runWebStandardMcp": "function", + "satisfiesRange": "function", + "scanSkillResources": "function", + "secureStoreConfigSchema": "object", + "secureStoreCustomBackendSchema": "object", + "secureStoreEncryptionSchema": "object", + "secureStoreScopeSchema": "object", + "sendFallbackNotification": "function", + "serializeElicitationBlob": "function", + "serializeHttpCookie": "function", + "serverInfoOptionsSchema": "object", + "serverManifestSchema": "object", + "sessionIdPayloadSchema": "object", + "sessionIdPresentedBy": "function", + "sessionIdSchema": "object", + "sessionOptionsSchema": "object", + "setNestedValue": "function", + "setServerlessHandler": "function", + "setServerlessHandlerError": "function", + "setServerlessHandlerPromise": "function", + "setSkillAuditFactory": "function", + "shouldCacheProviders": "function", + "shouldStopExecution": "function", + "skill": "function", + "skillCallerDir": "symbol", + "skillDir": "function", + "skillMetadataSchema": "object", + "skillsConfigAuthModeSchema": "object", + "skillsConfigEndpointConfigSchema": "object", + "skillsConfigOptionsSchema": "object", + "skippedAppBehaviorSchema": "object", + "sqliteOptionsSchema": "object", + "sqliteStorageProvider": "function", + "startupFailureResponse": "function", + "staticAuthOptionsSchema": "object", + "storageProviderSchema": "object", + "supportsStreaming": "function", + "surfaceHash": "function", + "timeoutConfigSchema": "object", + "timerLimitMessage": "function", + "toJSONSchema": "function", + "toMcpError": "function", + "toRegistryAuthRecord": "function", + "toWireShape": "function", + "tokenRefreshConfigSchema": "object", + "tokenStorageConfigSchema": "object", + "tokenStorageSqliteSchema": "object", + "tool": "function", + "toolPaginationOptionsSchema": "object", + "transparentAuthOptionsSchema": "object", + "transportOptionsSchema": "object", + "trimSlashes": "function", + "tryDecryptStoredValue": "function", + "tryGetConfig": "function", + "upstreamProviderSchema": "object", + "urlToSafeId": "function", + "userClaimSchema": "object", + "validateElicitationContent": "function", + "validateHeaderParams": "function", + "validateMcpSessionHeader": "function", + "validateRemoteUrl": "function", + "vercelKvProviderSchema": "object", + "withConfig": "function", + "withHttpHeaders": "function", + "withRetry": "function", + "withTimeout": "function", + "withUndeclaredKeysAllowed": "function", + "workflow": "function", + "z": "object" +} diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/helpers/initialized-modules.cjs b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/initialized-modules.cjs new file mode 100644 index 000000000..514125859 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/initialized-modules.cjs @@ -0,0 +1,65 @@ +// Loads the SDK's CommonJS build with esbuild's module-init helper wrapped, so the source modules it initializes are +// recorded, then prints, as JSON, the ones initialized by loading it and by each step after: +// node initialized-modules.cjs [read:,...] [fetch: ]... +const Module = require('node:module'); +const { readFileSync, realpathSync } = require('node:fs'); +const path = require('node:path'); + +const ESM_INIT_HELPER = + 'var __esm=(fn,res,err)=>function __init(){if(err)throw err[0];try{return fn&&(res=(0,fn[__getOwnPropNames(fn)[0]])(fn=0)),res}catch(e){throw err=[e],e}};'; +const RECORD_INITS = + 'var __esmUnrecorded=__esm;__esm=(fn,res,err)=>{const source=__getOwnPropNames(fn)[0];const init=__esmUnrecorded(fn,res,err);return function(){globalThis.__sdkInitialized.add(source);return init()}};'; + +const [entryArg, ...steps] = process.argv.slice(2); +const entry = realpathSync(path.resolve(entryArg)); +const code = readFileSync(entry, 'utf8'); +if (!code.includes(ESM_INIT_HELPER)) throw new Error(`esbuild's __esm helper not found in ${entry}`); + +const initialized = (globalThis.__sdkInitialized = new Set()); +const sdkModule = new Module(entry, module); +sdkModule.filename = entry; +sdkModule.paths = Module._nodeModulePaths(path.dirname(entry)); +require.cache[entry] = sdkModule; +sdkModule._compile(code.replace(ESM_INIT_HELPER, ESM_INIT_HELPER + RECORD_INITS), entry); +sdkModule.loaded = true; +const sdk = sdkModule.exports; + +let seen = new Set(); +function initializedSince() { + const added = [...initialized].filter((source) => !seen.has(source)); + seen = new Set(initialized); + return added; +} + +async function main() { + const report = [{ step: 'load', initialized: initializedSince() }]; + let handler; + for (const step of steps) { + const [kind, arg] = [step.slice(0, step.indexOf(':')), step.slice(step.indexOf(':') + 1)]; + if (kind === 'read') { + for (const name of arg.split(',')) void sdk[name]; + } else if (kind === 'fetch') { + handler ??= await sdk.FrontMcpInstance.createFetchHandler({ + info: { name: 'initialized-modules', version: '1.0.0' }, + apps: [sdk.app({ id: 'empty', name: 'Empty' })], + logging: { level: sdk.LogLevel.Off }, + }); + if (arg) { + const [method, url] = arg.split(' '); + await (await handler(new Request(`http://localhost${url}`, { method }))).arrayBuffer(); + } + } else { + throw new Error(`unknown step ${step}`); + } + report.push({ step, initialized: initializedSince() }); + } + process.stdout.write(JSON.stringify(report)); +} + +main().then( + () => process.exit(0), + (error) => { + process.stderr.write(String(error?.stack ?? error)); + process.exit(1); + }, +); diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/helpers/root-exports.mjs b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/root-exports.mjs new file mode 100644 index 000000000..9e5f4df54 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/root-exports.mjs @@ -0,0 +1,22 @@ +// Loads an SDK entry with require() or import() and prints, as JSON, the kind of each of its exports: node root-exports.mjs +import { createRequire } from 'node:module'; +import { pathToFileURL } from 'node:url'; + +const [entry, loader] = process.argv.slice(2); +const sdk = loader === 'require' ? createRequire(import.meta.url)(entry) : await import(pathToFileURL(entry).href); + +function kindOf(value) { + if (value === null) return 'null'; + if (Array.isArray(value)) return 'array'; + if (typeof value === 'function') { + return /^class\b/.test(Function.prototype.toString.call(value)) ? 'class' : 'function'; + } + return typeof value; +} + +const kinds = Object.fromEntries( + Object.keys(sdk) + .sort() + .map((name) => [name, kindOf(sdk[name])]), +); +process.stdout.write(JSON.stringify(kinds)); diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/helpers/subsystem-hooks.mjs b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/subsystem-hooks.mjs new file mode 100644 index 000000000..817d5d764 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/helpers/subsystem-hooks.mjs @@ -0,0 +1,192 @@ +// Requires the SDK's CommonJS build, runs a job, a workflow, a channel notification, the channel list, a remote app's +// tool, an ESM package's tool and a request to each OAuth route of a public-mode server, and prints, as JSON, the +// flows a server plugin's hooks saw, on its last line: node subsystem-hooks.mjs +import { createServer } from 'node:http'; +import { createRequire } from 'node:module'; + +const [entry] = process.argv.slice(2); +const sdk = createRequire(import.meta.url)(entry); +const { + App, + Channel, + ChannelContext, + FlowHooksOf, + FrontMcpInstance, + Job, + JobContext, + LogLevel, + Plugin, + Tool, + ToolContext, +} = sdk; +const { Workflow, connect, z } = sdk; + +const OAUTH_ROUTES = { + 'oauth:authorize': ['GET', '/oauth/authorize'], + 'oauth:token': ['POST', '/oauth/token'], + 'oauth:userinfo': ['GET', '/oauth/userinfo'], + 'oauth:callback': ['GET', '/oauth/callback'], + 'oauth:connect': ['GET', '/oauth/connect'], + 'oauth:register': ['POST', '/oauth/register'], + 'oauth:provider-callback': ['GET', '/oauth/provider/upstream/callback'], + 'oauth:auth-ui-extra': ['POST', '/oauth/ui/extra'], +}; + +const seen = []; + +function decorate(target, name, decorator) { + decorator(target, name, Object.getOwnPropertyDescriptor(target, name)); +} + +class SubsystemHooksPlugin { + recordJob(flowCtx) { + const { job, workflow } = flowCtx.state; + seen.push(workflow ? `jobs:execute-job ${job.name} in ${workflow.name}` : `jobs:execute-job ${job.name}`); + } + recordChannelSend() { + seen.push('channels:send-notification'); + } + recordChannelList() { + seen.push('channels:list'); + } + recordToolCall(flowCtx) { + const tool = flowCtx.rawInput.request.params.name; + if (tool.startsWith('remote') || tool.startsWith('esm')) seen.push(`tools:call-tool ${tool}`); + } +} +decorate(SubsystemHooksPlugin.prototype, 'recordJob', FlowHooksOf('jobs:execute-job').Did('updateRunState')); +decorate(SubsystemHooksPlugin.prototype, 'recordChannelSend', FlowHooksOf('channels:send-notification').Will('send')); +decorate(SubsystemHooksPlugin.prototype, 'recordChannelList', FlowHooksOf('channels:list').Will('listChannels')); +decorate(SubsystemHooksPlugin.prototype, 'recordToolCall', FlowHooksOf('tools:call-tool').Will('parseInput')); +for (const flow of Object.keys(OAUTH_ROUTES)) { + const method = `record:${flow}`; + SubsystemHooksPlugin.prototype[method] = () => seen.push(flow); + decorate(SubsystemHooksPlugin.prototype, method, FlowHooksOf(flow).Will('checkIpFilter')); +} +Plugin({ name: 'subsystem-hooks', description: 'Records the flows of each subsystem that ran' })(SubsystemHooksPlugin); + +class GreetJob extends JobContext { + async execute({ name }) { + return { message: `Hello, ${name}` }; + } +} +Job({ name: 'greet', inputSchema: { name: z.string() }, outputSchema: { message: z.string() } })(GreetJob); + +class GreetWorkflow {} +Workflow({ name: 'greet-twice', steps: [{ id: 'greet', jobName: 'greet', input: { name: 'Workflow' } }] })( + GreetWorkflow, +); + +class AlertChannel extends ChannelContext { + async onEvent(payload) { + return { content: `alert: ${payload.message}` }; + } +} +Channel({ name: 'alerts', source: { type: 'app-event', event: 'alert' } })(AlertChannel); + +class AlertTool extends ToolContext { + async execute() { + this.scope.channelEventBus.emit('alert', { message: 'raised' }); + await this.scope.runFlow('channels:list', {}); + return 'raised'; + } +} +Tool({ name: 'raise-alert', inputSchema: {} })(AlertTool); + +class SubsystemsApp {} +App({ + name: 'Subsystems', + jobs: [GreetJob], + workflows: [GreetWorkflow], + channels: [AlertChannel], + tools: [AlertTool], +})(SubsystemsApp); + +class ShoutTool extends ToolContext { + async execute() { + return 'HELLO'; + } +} +Tool({ name: 'shout', inputSchema: {} })(ShoutTool); +class RemoteApp {} +App({ name: 'Remote', tools: [ShoutTool] })(RemoteApp); + +function listen(server) { + return new Promise((resolveListen) => server.listen(0, '127.0.0.1', () => resolveListen(server.address().port))); +} + +const portProbe = createServer(); +const remotePort = await listen(portProbe); +await new Promise((resolveClose) => portProbe.close(resolveClose)); +const remote = await FrontMcpInstance.bootstrap({ + info: { name: 'remote', version: '1.0.0' }, + apps: [RemoteApp], + http: { port: remotePort }, + logging: { level: LogLevel.Off }, +}); + +const ESM_PACKAGE = '@frontmcp-e2e/wave-tools'; +const ESM_BUNDLE = `module.exports = { default: { name: '${ESM_PACKAGE}', version: '1.0.0', tools: [{ + name: 'wave', description: 'Waves', inputSchema: { type: 'object', properties: {} }, + execute: async () => ({ content: [{ type: 'text', text: 'wave' }] }), +}] } };`; +const esmPackages = createServer((request, response) => { + const path = decodeURIComponent(new URL(request.url, 'http://127.0.0.1').pathname).slice(1); + if (path === ESM_PACKAGE) { + response.writeHead(200, { 'Content-Type': 'application/json' }); + response.end( + JSON.stringify({ + name: ESM_PACKAGE, + 'dist-tags': { latest: '1.0.0' }, + versions: { '1.0.0': { name: ESM_PACKAGE, version: '1.0.0' } }, + time: { '1.0.0': new Date().toISOString() }, + }), + ); + } else if (path === `${ESM_PACKAGE}@1.0.0`) { + response.writeHead(200, { 'Content-Type': 'application/javascript' }); + response.end(ESM_BUNDLE); + } else { + response.writeHead(404); + response.end(); + } +}); +const esmPort = await listen(esmPackages); + +const config = { + info: { name: 'subsystem-hooks', version: '1.0.0' }, + loader: { url: `http://127.0.0.1:${esmPort}` }, + apps: [ + SubsystemsApp, + { name: 'remote', urlType: 'url', url: `http://127.0.0.1:${remotePort}/`, namespace: 'remote', standalone: false }, + App.esm(`${ESM_PACKAGE}@^1.0.0`, { namespace: 'esm' }), + ], + plugins: [SubsystemHooksPlugin], + jobs: { enabled: true }, + channels: { enabled: true }, + logging: { level: LogLevel.Off }, +}; + +const client = await connect(config); +try { + await client.callTool('execute_job', { name: 'greet', input: { name: 'Ada' } }); + await client.callTool('execute_workflow', { name: 'greet-twice' }); + await client.callTool('raise-alert', {}); + const tools = await client.listTools(); + for (const { name } of tools.filter((tool) => tool.name.startsWith('remote') || tool.name.startsWith('esm'))) { + await client.callTool(name, {}); + } + await new Promise((resolveDelivery) => setTimeout(resolveDelivery, 100)); +} finally { + await client.close(); +} + +const handler = await FrontMcpInstance.createFetchHandler(config); +for (const [method, path] of Object.values(OAUTH_ROUTES)) { + const response = await handler(new Request(`http://localhost${path}`, { method })); + await response.arrayBuffer(); +} + +await remote?.shutdown?.(); +esmPackages.close(); +process.stdout.write(`\n${JSON.stringify(seen)}\n`); +process.exit(0); diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/sdk-lazy-subsystems.e2e.spec.ts b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-lazy-subsystems.e2e.spec.ts new file mode 100644 index 000000000..0d9a2e377 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-lazy-subsystems.e2e.spec.ts @@ -0,0 +1,71 @@ +/** + * The SDK's CommonJS build sets up the subsystems a server may never use the first time one of their exports is read, + * and a public-mode server loads each of its OAuth flows with the first request that reaches it (#950). + * + * Needs the packages built: `nx run-many -t build -p sdk plugin-webmcp`. + */ +import { execFileSync } from 'node:child_process'; +import { resolve } from 'node:path'; + +import { workspaceRoot } from './helpers/browser-bundle'; + +type Step = { step: string; initialized: string[] }; + +const SUBSYSTEM_MODULE = /^libs\/sdk\/src\/(job|workflow|channel|remote-mcp|esm-loader)\//; +// Helpers the startup path itself uses: the decorators parse `Tool.esm()` specifiers, the scope normalizes app entries +const STARTUP_HELPERS = [ + 'libs/sdk/src/esm-loader/package-specifier.ts', + 'libs/sdk/src/esm-loader/module-specifiers.ts', + 'libs/sdk/src/job/job.utils.ts', + 'libs/sdk/src/channel/channel.utils.ts', + 'libs/sdk/src/channel/sources/completion-events.ts', +]; +const OAUTH_FLOW_MODULE = /^libs\/sdk\/src\/auth\/flows\/oauth\.[a-z-]+\.flow\.ts$/; + +function initializedModules(...steps: string[]): Step[] { + const runner = resolve(__dirname, 'helpers', 'initialized-modules.cjs'); + return JSON.parse( + execFileSync(process.execPath, [runner, resolve(workspaceRoot, 'libs/sdk/dist/index.js'), ...steps], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + maxBuffer: 16 * 1024 * 1024, + }), + ); +} + +describe('the subsystems of the SDK under require()', () => { + it('are not set up when the SDK loads', () => { + const [load] = initializedModules(); + + expect(load.initialized.length).toBeGreaterThan(0); + const subsystemModules = load.initialized.filter((source) => SUBSYSTEM_MODULE.test(source)); + expect(subsystemModules.filter((source) => !STARTUP_HELPERS.includes(source))).toEqual([]); + }); + + it.each([ + ['JobRegistry', 'libs/sdk/src/job/job.registry.ts'], + ['WorkflowRegistry', 'libs/sdk/src/workflow/workflow.registry.ts'], + ['ChannelRegistry', 'libs/sdk/src/channel/channel.registry.ts'], + ['McpClientService', 'libs/sdk/src/remote-mcp/mcp-client.service.ts'], + ['EsmModuleLoader', 'libs/sdk/src/esm-loader/esm-module-loader.ts'], + ])('are set up when %s is read', (exportName, source) => { + const [, read] = initializedModules(`read:${exportName}`); + + expect(read.initialized).toContain(source); + }); +}); + +describe('the OAuth flows of a public-mode server', () => { + it('load with the first request that reaches each one', () => { + const [, start, authorize, token] = initializedModules( + 'fetch:', + 'fetch:GET /oauth/authorize', + 'fetch:POST /oauth/token', + ); + const oauthFlows = (step: Step) => step.initialized.filter((source) => OAUTH_FLOW_MODULE.test(source)); + + expect(oauthFlows(start)).toEqual([]); + expect(oauthFlows(authorize)).toEqual(['libs/sdk/src/auth/flows/oauth.authorize.flow.ts']); + expect(oauthFlows(token)).toEqual(['libs/sdk/src/auth/flows/oauth.token.flow.ts']); + }); +}); diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/sdk-root-exports.e2e.spec.ts b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-root-exports.e2e.spec.ts new file mode 100644 index 000000000..57dc35188 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-root-exports.e2e.spec.ts @@ -0,0 +1,42 @@ +/** + * The SDK's CommonJS and ESM entry points export the same names, each of the same kind, as the list pinned in + * `fixtures/sdk-root-exports.json` (#950): loading a subsystem on first use changes none of them. + * + * Needs the packages built: `nx run-many -t build -p sdk plugin-webmcp`. + */ +import { execFileSync } from 'node:child_process'; +import { resolve } from 'node:path'; + +import { readJSON } from '@frontmcp/utils'; + +import { workspaceRoot } from './helpers/browser-bundle'; + +const pinnedExports = () => readJSON>(resolve(__dirname, 'fixtures', 'sdk-root-exports.json')); + +function exportKinds(entry: string, loader: 'require' | 'import'): Record { + const runner = resolve(__dirname, 'helpers', 'root-exports.mjs'); + return JSON.parse( + execFileSync(process.execPath, [runner, resolve(workspaceRoot, entry), loader], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + maxBuffer: 16 * 1024 * 1024, + }), + ); +} + +describe('the root exports of @frontmcp/sdk', () => { + it('are the pinned names and kinds under require()', async () => { + expect(exportKinds('libs/sdk/dist/index.js', 'require')).toEqual(await pinnedExports()); + }); + + it("are the pinned names and kinds when an ESM import loads the CommonJS build, as Node's export detection sees it", async () => { + expect(exportKinds('libs/sdk/dist/index.js', 'import')).toEqual({ + ...(await pinnedExports()), + 'module.exports': 'object', + }); + }); + + it('are the pinned names and kinds under import', async () => { + expect(exportKinds('libs/sdk/dist/esm/index.mjs', 'import')).toEqual(await pinnedExports()); + }); +}); diff --git a/apps/e2e/demo-e2e-bundle-size/e2e/sdk-subsystem-hooks.e2e.spec.ts b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-subsystem-hooks.e2e.spec.ts new file mode 100644 index 000000000..01bf184a9 --- /dev/null +++ b/apps/e2e/demo-e2e-bundle-size/e2e/sdk-subsystem-hooks.e2e.spec.ts @@ -0,0 +1,43 @@ +/** + * With the SDK's CommonJS build, a plugin's hooks on the flows of the subsystems it sets up on first use still run: + * jobs and workflow steps, channel notifications and the channel list, the tools of a remote app and of an ESM package, + * and every OAuth flow of a public-mode server (#950). + * + * Needs the packages built: `nx run-many -t build -p sdk plugin-webmcp`. + */ +import { execFileSync } from 'node:child_process'; +import { resolve } from 'node:path'; + +import { workspaceRoot } from './helpers/browser-bundle'; + +function flowsTheHooksSaw(): string[] { + const runner = resolve(__dirname, 'helpers', 'subsystem-hooks.mjs'); + const output = execFileSync(process.execPath, [runner, resolve(workspaceRoot, 'libs/sdk/dist/index.js')], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'pipe'], + }); + return JSON.parse(output.trim().split('\n').at(-1) ?? '[]'); +} + +describe('hooks on the flows of the subsystems the SDK sets up on first use', () => { + it('run under require()', () => { + expect(flowsTheHooksSaw().sort()).toEqual( + [ + 'jobs:execute-job greet', + 'jobs:execute-job greet in greet-twice', + 'channels:send-notification', + 'channels:list', + 'tools:call-tool remote:shout', + 'tools:call-tool esm:wave', + 'oauth:authorize', + 'oauth:token', + 'oauth:userinfo', + 'oauth:callback', + 'oauth:connect', + 'oauth:register', + 'oauth:provider-callback', + 'oauth:auth-ui-extra', + ].sort(), + ); + }); +}); diff --git a/libs/sdk/esbuild.cjs.config.js b/libs/sdk/esbuild.cjs.config.js new file mode 100644 index 000000000..eacd34ce7 --- /dev/null +++ b/libs/sdk/esbuild.cjs.config.js @@ -0,0 +1,55 @@ +// The esbuild options of the CommonJS build (build-cjs). Its root takes src/lazy-subsystems.ts for ./subsystems, so a +// CommonJS process sets up jobs, workflows, channels, remote MCP and the ESM loader when it first reads their exports. +const path = require('node:path'); + +const ROOT_ENTRY = path.join('libs', 'sdk', 'src', 'index.ts'); + +const lazySubsystems = { + name: 'lazy-subsystems', + setup(build) { + let swapped = false; + build.onResolve({ filter: /^\.\/subsystems$/ }, (args) => { + if (!args.importer.endsWith(ROOT_ENTRY)) return undefined; + swapped = true; + return { path: path.join(args.resolveDir, 'lazy-subsystems.ts') }; + }); + build.onEnd(() => { + if (!swapped) throw new Error(`${ROOT_ENTRY} no longer imports ./subsystems for lazy-subsystems to swap`); + }); + }, +}; + +module.exports = { + minifyWhitespace: true, + outExtension: { '.js': '.js' }, + external: [ + '#js-yaml', + '#semver', + '#uipack-render', + '#protocol-client', + '@frontmcp/sdk', + '@frontmcp/utils', + '@frontmcp/di', + '@frontmcp/lazy-zod', + '@frontmcp/uipack', + '@frontmcp/uipack/*', + '@frontmcp/auth', + '@frontmcp/guard', + '@frontmcp/protocol', + '@frontmcp/protocol/*', + '@frontmcp/observability', + '@opentelemetry/api', + '@opentelemetry/sdk-trace-base', + 'ioredis', + 'jose', + 'reflect-metadata', + 'zod', + 'express', + 'cors', + 'raw-body', + 'content-type', + '@vercel/kv', + '#import-computed', + ], + plugins: [lazySubsystems], +}; diff --git a/libs/sdk/project.json b/libs/sdk/project.json index 1cc00470f..9ef6533b9 100644 --- a/libs/sdk/project.json +++ b/libs/sdk/project.json @@ -31,39 +31,7 @@ "libs/sdk/src/third-party/import-protocol-client.ts" ], "assets": ["libs/sdk/README.md", "LICENSE", "libs/sdk/package.json"], - "esbuildOptions": { - "minifyWhitespace": true, - "outExtension": { ".js": ".js" }, - "external": [ - "#js-yaml", - "#semver", - "#uipack-render", - "#protocol-client", - "@frontmcp/sdk", - "@frontmcp/utils", - "@frontmcp/di", - "@frontmcp/lazy-zod", - "@frontmcp/uipack", - "@frontmcp/uipack/*", - "@frontmcp/auth", - "@frontmcp/guard", - "@frontmcp/protocol", - "@frontmcp/protocol/*", - "@frontmcp/observability", - "@opentelemetry/api", - "@opentelemetry/sdk-trace-base", - "ioredis", - "jose", - "reflect-metadata", - "zod", - "express", - "cors", - "raw-body", - "content-type", - "@vercel/kv", - "#import-computed" - ] - } + "esbuildConfig": "libs/sdk/esbuild.cjs.config.js" } }, "build-esm": { diff --git a/libs/sdk/src/__tests__/lazy-subsystems.spec.ts b/libs/sdk/src/__tests__/lazy-subsystems.spec.ts new file mode 100644 index 000000000..4c56ba15c --- /dev/null +++ b/libs/sdk/src/__tests__/lazy-subsystems.spec.ts @@ -0,0 +1,35 @@ +/** + * The CommonJS build's `./subsystems` (`lazy-subsystems.ts`) exports what `subsystems.ts` exports, and loads a + * subsystem the first time one of its exports is read (#950). + */ +describe('lazy-subsystems', () => { + afterEach(() => { + jest.dontMock('../job'); + jest.resetModules(); + }); + + it('exports the values subsystems.ts exports', () => { + const lazySubsystems = require('../lazy-subsystems'); + const subsystems = require('../subsystems'); + + expect(Object.keys(lazySubsystems).sort()).toEqual(Object.keys(subsystems).sort()); + for (const name of Object.keys(subsystems)) { + expect(lazySubsystems[name]).toBe(subsystems[name]); + } + }); + + it('loads a subsystem the first time one of its exports is read', () => { + const JobRegistry = class {}; + const loadJob = jest.fn(() => ({ JobRegistry })); + jest.doMock('../job', loadJob); + + jest.isolateModules(() => { + const lazySubsystems = require('../lazy-subsystems'); + expect(loadJob).not.toHaveBeenCalled(); + + expect(lazySubsystems.JobRegistry).toBe(JobRegistry); + expect(lazySubsystems.JobRegistry).toBe(JobRegistry); + expect(loadJob).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/libs/sdk/src/auth/flows/__tests__/oauth-flows.spec.ts b/libs/sdk/src/auth/flows/__tests__/oauth-flows.spec.ts new file mode 100644 index 000000000..bd81507c2 --- /dev/null +++ b/libs/sdk/src/auth/flows/__tests__/oauth-flows.spec.ts @@ -0,0 +1,69 @@ +/** + * The OAuth flows are registered by name and route, and each one's class loads with the first request that reaches it + * (#950). A public-mode server still runs a plugin's hooks on every one of them. + */ +import 'reflect-metadata'; + +import { FlowHooksOf, LogLevel, Plugin, type FlowType } from '../../../common'; +import { deferredFlowLoader } from '../../../flows/deferred-flow'; +import { collectFlowMetadata } from '../../../flows/flow.utils'; +import { FrontMcpInstance } from '../../../front-mcp/front-mcp'; +import { oauthFlows } from '../oauth-flows'; + +const ROUTES = [ + ['oauth:authorize', 'GET', '/oauth/authorize'], + ['oauth:token', 'POST', '/oauth/token'], + ['oauth:userinfo', 'GET', '/oauth/userinfo'], + ['oauth:callback', 'GET', '/oauth/callback'], + ['oauth:connect', 'POST', '/oauth/connect'], + ['oauth:register', 'POST', '/oauth/register'], + ['oauth:provider-callback', 'GET', '/oauth/provider/upstream/callback'], + ['oauth:auth-ui-extra', 'POST', '/oauth/ui/extra'], +] as const; + +const hooked: string[] = []; + +function loadFlowClass(flow: FlowType): Promise { + const load = deferredFlowLoader(flow); + if (!load) throw new Error(`${flow.name} is not a deferred flow`); + return load(); +} + +class OAuthHooksPlugin {} +for (const [flow] of ROUTES) { + const method = `will:${flow}`; + const descriptor: PropertyDescriptor = { value: () => hooked.push(flow) }; + Object.defineProperty(OAuthHooksPlugin.prototype, method, descriptor); + FlowHooksOf(flow).Will('checkIpFilter')(OAuthHooksPlugin.prototype, method, descriptor); +} +Plugin({ name: 'oauth-hooks' })(OAuthHooksPlugin); + +describe('oauthFlows', () => { + it('declares each flow by the name and route of the class it loads', async () => { + const declared = oauthFlows.map((flow) => collectFlowMetadata(flow)); + const loaded = await Promise.all(oauthFlows.map(async (flow) => collectFlowMetadata(await loadFlowClass(flow)))); + + expect(declared.map(({ name, middleware }) => ({ name, middleware }))).toEqual( + loaded.map(({ name, middleware }) => ({ + name, + middleware: { method: middleware?.method, path: middleware?.path }, + })), + ); + expect(declared.map(({ name }) => name)).toEqual(ROUTES.map(([flow]) => flow)); + }); + + it('runs the hooks of every OAuth flow of a public-mode server', async () => { + const handler = await FrontMcpInstance.createFetchHandler({ + info: { name: 'oauth-flows', version: '1.0.0' }, + apps: [], + plugins: [OAuthHooksPlugin], + logging: { level: LogLevel.Off }, + }); + + for (const [, method, path] of ROUTES) { + await (await handler(new Request(`http://localhost${path}`, { method }))).arrayBuffer(); + } + + expect(hooked).toEqual(ROUTES.map(([flow]) => flow)); + }); +}); diff --git a/libs/sdk/src/auth/flows/oauth-flows.ts b/libs/sdk/src/auth/flows/oauth-flows.ts new file mode 100644 index 000000000..0a80cc75f --- /dev/null +++ b/libs/sdk/src/auth/flows/oauth-flows.ts @@ -0,0 +1,29 @@ +import { deferredFlow } from '../../flows/deferred-flow'; + +/** The OAuth flows, in route order. Each is registered by name and route, and loads with its first request. */ +export const oauthFlows = [ + deferredFlow({ name: 'oauth:authorize', middleware: { method: 'GET', path: '/oauth/authorize' } }, () => + import('./oauth.authorize.flow.js').then((flow) => flow.OauthAuthorizeFlow), + ), + deferredFlow({ name: 'oauth:token', middleware: { method: 'POST', path: '/oauth/token' } }, () => + import('./oauth.token.flow.js').then((flow) => flow.OauthTokenFlow), + ), + deferredFlow({ name: 'oauth:userinfo', middleware: { method: 'GET', path: '/oauth/userinfo' } }, () => + import('./oauth.userinfo.flow.js').then((flow) => flow.OauthUserInfoFlow), + ), + deferredFlow({ name: 'oauth:callback', middleware: { path: '/oauth/callback' } }, () => + import('./oauth.callback.flow.js').then((flow) => flow.OauthCallbackFlow), + ), + deferredFlow({ name: 'oauth:connect', middleware: { path: '/oauth/connect' } }, () => + import('./oauth.connect.flow.js').then((flow) => flow.OauthConnectFlow), + ), + deferredFlow({ name: 'oauth:register', middleware: { method: 'POST', path: '/oauth/register' } }, () => + import('./oauth.register.flow.js').then((flow) => flow.OauthRegisterFlow), + ), + deferredFlow({ name: 'oauth:provider-callback', middleware: { path: '/oauth/provider/:providerId/callback' } }, () => + import('./oauth.provider-callback.flow.js').then((flow) => flow.OauthProviderCallbackFlow), + ), + deferredFlow({ name: 'oauth:auth-ui-extra', middleware: { method: 'POST', path: '/oauth/ui/extra' } }, () => + import('./oauth.auth-ui.flow.js').then((flow) => flow.OauthAuthUiExtraFlow), + ), +]; diff --git a/libs/sdk/src/auth/flows/oauth.auth-ui.flow.ts b/libs/sdk/src/auth/flows/oauth.auth-ui.flow.ts index c29bea2cc..edf204c7a 100644 --- a/libs/sdk/src/auth/flows/oauth.auth-ui.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.auth-ui.flow.ts @@ -182,3 +182,5 @@ function timingSafeEqualStr(a: string, b: string): boolean { } return diff === 0; } + +export { OauthAuthUiExtraFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.authorize.flow.ts b/libs/sdk/src/auth/flows/oauth.authorize.flow.ts index 2afc17395..c4ad1631c 100644 --- a/libs/sdk/src/auth/flows/oauth.authorize.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.authorize.flow.ts @@ -1446,3 +1446,5 @@ export default class OauthAuthorizeFlow extends FlowBase { `; } } + +export { OauthAuthorizeFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.callback.flow.ts b/libs/sdk/src/auth/flows/oauth.callback.flow.ts index 53bfc73ab..912c98ac0 100644 --- a/libs/sdk/src/auth/flows/oauth.callback.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.callback.flow.ts @@ -1617,3 +1617,5 @@ function timingSafeEqualStr(a: string, b: string): boolean { } return diff === 0; } + +export { OauthCallbackFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.connect.flow.ts b/libs/sdk/src/auth/flows/oauth.connect.flow.ts index 3b59a4d27..d4bb0e629 100644 --- a/libs/sdk/src/auth/flows/oauth.connect.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.connect.flow.ts @@ -386,3 +386,5 @@ export default class OauthConnectFlow extends FlowBase { `; } } + +export { OauthConnectFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts b/libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts index 176c85d71..16d0e139c 100644 --- a/libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.provider-callback.flow.ts @@ -1074,3 +1074,5 @@ export default class OauthProviderCallbackFlow extends FlowBase { function timingSafeEqualStr(a: string, b: string): boolean { return timingSafeEqual(sha256(a), sha256(b)); } + +export { OauthProviderCallbackFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.register.flow.ts b/libs/sdk/src/auth/flows/oauth.register.flow.ts index 1736ba407..8148431a2 100644 --- a/libs/sdk/src/auth/flows/oauth.register.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.register.flow.ts @@ -381,3 +381,5 @@ export default class OauthRegisterFlow extends FlowBase { // no-op; httpRespond.json enforces shape } } + +export { OauthRegisterFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.token.flow.ts b/libs/sdk/src/auth/flows/oauth.token.flow.ts index 57a5d1433..623db19fd 100644 --- a/libs/sdk/src/auth/flows/oauth.token.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.token.flow.ts @@ -571,3 +571,5 @@ export default class OauthTokenFlow extends FlowBase { return computeResource(this.rawInput.request, this.scope.entryPath, this.scope.routeBase); } } + +export { OauthTokenFlow }; diff --git a/libs/sdk/src/auth/flows/oauth.userinfo.flow.ts b/libs/sdk/src/auth/flows/oauth.userinfo.flow.ts index 0791dd152..e8cfa6135 100644 --- a/libs/sdk/src/auth/flows/oauth.userinfo.flow.ts +++ b/libs/sdk/src/auth/flows/oauth.userinfo.flow.ts @@ -155,3 +155,5 @@ export default class OauthUserInfoFlow extends FlowBase { this.respond(httpRespond.json(body)); } } + +export { OauthUserInfoFlow }; diff --git a/libs/sdk/src/auth/instances/instance.local-primary-auth.ts b/libs/sdk/src/auth/instances/instance.local-primary-auth.ts index db22fe219..1193070f2 100644 --- a/libs/sdk/src/auth/instances/instance.local-primary-auth.ts +++ b/libs/sdk/src/auth/instances/instance.local-primary-auth.ts @@ -69,14 +69,7 @@ import { importWithRequireFallback } from '../../utils/dynamic-import.utils'; import { CimdService } from '../cimd'; import { createCredentialsProviders } from '../credentials'; import { credentialsContextExtension } from '../credentials/credentials.context-extension'; -import OauthAuthUiExtraFlow from '../flows/oauth.auth-ui.flow'; -import OauthAuthorizeFlow from '../flows/oauth.authorize.flow'; -import OauthCallbackFlow from '../flows/oauth.callback.flow'; -import OauthConnectFlow from '../flows/oauth.connect.flow'; -import OauthProviderCallbackFlow from '../flows/oauth.provider-callback.flow'; -import OauthRegisterFlow from '../flows/oauth.register.flow'; -import OauthTokenFlow from '../flows/oauth.token.flow'; -import OauthUserInfoFlow from '../flows/oauth.userinfo.flow'; +import { oauthFlows } from '../flows/oauth-flows'; import SessionVerifyFlow from '../flows/session.verify.flow'; import WellKnownJwksFlow from '../flows/well-known.jwks.flow'; import WellKnownAsFlow from '../flows/well-known.oauth-authorization-server.flow'; @@ -1582,15 +1575,7 @@ export class LocalPrimaryAuth extends FrontMcpAuth { WellKnownAsFlow /** /.well-known/oauth-authorization-server */, WellKnownJwksFlow /** /.well-known/jwks.json */, SessionVerifyFlow /** Session verification flow */, - - OauthAuthorizeFlow /** GET /oauth/authorize */, - OauthTokenFlow /** POST /oauth/token */, - OauthUserInfoFlow /** GET /oauth/userinfo - OIDC userinfo */, - OauthCallbackFlow /** GET /oauth/callback - login callback */, - OauthConnectFlow /** GET|POST /oauth/connect - mid-session add-credential (Checkpoint 3b) */, - OauthRegisterFlow /** POST /oauth/register */, - OauthProviderCallbackFlow /** GET /oauth/provider/:providerId/callback */, - OauthAuthUiExtraFlow /** POST /oauth/ui/extra — @AuthExtra validated-field submit (#469) */, + ...oauthFlows, ); } diff --git a/libs/sdk/src/flows/__tests__/deferred-flow.spec.ts b/libs/sdk/src/flows/__tests__/deferred-flow.spec.ts new file mode 100644 index 000000000..7604dc375 --- /dev/null +++ b/libs/sdk/src/flows/__tests__/deferred-flow.spec.ts @@ -0,0 +1,187 @@ +/** + * A deferred flow is registered under its name and route like any flow, and its class loads the first time a request + * reaches the route or the flow runs (#950). Hooks on its stages run as they do on a flow registered by its class. + */ +import 'reflect-metadata'; + +import { z, ZodError } from '@frontmcp/lazy-zod'; + +import { + Flow, + FlowBase, + FlowHooksOf, + LogLevel, + Plugin, + type FlowPlan, + type FlowRunOptions, + type FlowType, + type ServerRequest, +} from '../../common'; +import { FrontMcpInstance } from '../../front-mcp/front-mcp'; +import type { Scope } from '../../scope/scope.instance'; +import { runMatchingHttpFlowWeb } from '../../transport/web-fetch-handler'; +import { deferredFlow } from '../deferred-flow'; + +const name = 'test:deferred-greet' as const; +const inputSchema = z.object({ name: z.string() }); +const outputSchema = z.object({ message: z.string() }); +const stateSchema = z.object({}); +const plan = { execute: ['greet'] } as const satisfies FlowPlan; + +declare global { + interface ExtendFlows { + 'test:deferred-greet': FlowRunOptions< + GreetFlow, + typeof plan, + typeof inputSchema, + typeof outputSchema, + typeof stateSchema + >; + 'test:deferred-guarded': FlowRunOptions< + GuardedFlow, + typeof plan, + typeof inputSchema, + typeof outputSchema, + typeof stateSchema + >; + } +} + +const GreetHook = FlowHooksOf(name); + +@Flow({ name, plan, inputSchema, outputSchema, access: 'public', middleware: { method: 'POST', path: '/greet' } }) +class GreetFlow extends FlowBase { + @GreetHook.Stage('greet') + async greet() { + this.respond({ message: `Hello, ${this.input.name}` }); + } +} + +const routeGuard = jest.fn(async () => true); +const classGuard = jest.fn(async () => true); + +@Flow({ + name: 'test:deferred-guarded', + plan, + inputSchema, + outputSchema, + access: 'public', + middleware: { path: '/guarded', canActivate: [routeGuard] }, +}) +class GuardedFlow extends FlowBase<'test:deferred-guarded'> { + static canActivate = classGuard; + + @(FlowHooksOf('test:deferred-guarded').Stage('greet')) + async greet() { + this.respond({ message: 'guarded' }); + } +} + +const hooked: string[] = []; + +@Plugin({ name: 'greet-hooks' }) +class GreetHooksPlugin { + @GreetHook.Will('greet') + willGreet() { + hooked.push('will greet'); + } +} + +function request(method: ServerRequest['method'], path: string): ServerRequest { + return { method, path, url: path, headers: {}, query: {}, body: undefined } as ServerRequest; +} + +describe('deferredFlow', () => { + let scope: Scope; + let load: jest.Mock, []>; + + beforeEach(async () => { + hooked.length = 0; + routeGuard.mockClear(); + classGuard.mockClear(); + load = jest.fn(async () => GreetFlow); + const frontMcp = await FrontMcpInstance.createForGraph({ + info: { name: 'deferred-flow', version: '1.0.0' }, + apps: [], + plugins: [GreetHooksPlugin], + logging: { level: LogLevel.Off }, + }); + scope = frontMcp.getScopes()[0] as Scope; + await scope.registryFlows(deferredFlow({ name, middleware: { method: 'POST', path: '/greet' } }, load)); + }); + + it('registers the flow without loading its class', () => { + expect(load).not.toHaveBeenCalled(); + }); + + it('loads its class once when it runs, and runs its stages and hooks', async () => { + const [first, second] = await Promise.all([ + scope.runFlowForOutput(name, { name: 'Ada' }), + scope.runFlowForOutput(name, { name: 'Lin' }), + ]); + + expect(first).toEqual({ message: 'Hello, Ada' }); + expect(second).toEqual({ message: 'Hello, Lin' }); + expect(hooked).toEqual(['will greet', 'will greet']); + expect(load).toHaveBeenCalledTimes(1); + }); + + it('validates input with the schema of the class it loads', async () => { + await expect(scope.runFlowForOutput(name, { name: 42 } as never)).rejects.toThrow(ZodError); + }); + + it('leaves its class unloaded for a request to another route or with another method', async () => { + await scope.findHttpFlowName(request('POST', '/elsewhere')); + await scope.findHttpFlowName(request('GET', '/greet')); + + expect(load).not.toHaveBeenCalled(); + }); + + it('runs each guard of the class it loads once per request', async () => { + await scope.registryFlows( + deferredFlow({ name: 'test:deferred-guarded', middleware: { path: '/guarded' } }, async () => GuardedFlow), + ); + + await scope.findHttpFlowName(request('GET', '/guarded')); + await scope.findHttpFlowName(request('GET', '/guarded')); + + expect(routeGuard).toHaveBeenCalledTimes(2); + expect(classGuard).toHaveBeenCalledTimes(2); + }); + + it('loads its class to answer a request to its route', async () => { + await expect(scope.findHttpFlowName(request('POST', '/greet'))).resolves.toBe(name); + + expect(load).toHaveBeenCalledTimes(1); + }); + + describe('when its class fails to load', () => { + const failingName = 'test:deferred-guarded' as const; + const failedLoad = async (): Promise => { + throw new Error('flow module failed to load'); + }; + const failing = () => deferredFlow({ name: failingName, middleware: { path: '/failing' } }, failedLoad); + + it('answers a Web request to its route with a 500', async () => { + await scope.registryFlows(failing()); + + const response = await runMatchingHttpFlowWeb(scope, new Request('http://localhost/failing')); + + expect(response?.status).toBe(500); + }); + + it('answers a request its middleware receives with a 500', async () => { + const server = scope.providers.getActiveServer(); + const register = jest.spyOn(server, 'registerMiddleware'); + await scope.registryFlows(failing()); + const handler = register.mock.calls.find(([path]) => path === '/failing')?.[1]; + const response = { setHeader: jest.fn(), status: jest.fn().mockReturnThis(), end: jest.fn() }; + const next = jest.fn(); + + await handler?.(request('GET', '/failing'), response as never, next); + + expect(response.status).toHaveBeenCalledWith(500); + expect(next).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/libs/sdk/src/flows/deferred-flow.ts b/libs/sdk/src/flows/deferred-flow.ts new file mode 100644 index 000000000..2f85bb0cf --- /dev/null +++ b/libs/sdk/src/flows/deferred-flow.ts @@ -0,0 +1,29 @@ +import 'reflect-metadata'; + +import { FlowBase, FrontMcpFlowTokens, type FlowName, type FlowType, type HttpMethod } from '../common'; + +/** What a deferred flow is registered by before its class loads: its name and its route. */ +export interface DeferredFlowDeclaration { + name: FlowName; + middleware: { path: string; method?: HttpMethod }; +} + +const flowClassLoaders = new WeakMap Promise>(); + +/** + * A flow registered under its name and route like any flow, whose class loads the first time a request reaches the + * route or the flow runs. + */ +export function deferredFlow(declaration: DeferredFlowDeclaration, load: () => Promise): FlowType { + class DeferredFlow extends FlowBase {} + Reflect.defineMetadata(FrontMcpFlowTokens.type, true, DeferredFlow); + Reflect.defineMetadata(FrontMcpFlowTokens.name, declaration.name, DeferredFlow); + Reflect.defineMetadata(FrontMcpFlowTokens.middleware, declaration.middleware, DeferredFlow); + flowClassLoaders.set(DeferredFlow, load); + return DeferredFlow; +} + +/** The loader of the class a flow registered with {@link deferredFlow} stands for. */ +export function deferredFlowLoader(flowClass: FlowType): (() => Promise) | undefined { + return flowClassLoaders.get(flowClass); +} diff --git a/libs/sdk/src/flows/flow.instance.ts b/libs/sdk/src/flows/flow.instance.ts index a8b5b2436..5d3015560 100644 --- a/libs/sdk/src/flows/flow.instance.ts +++ b/libs/sdk/src/flows/flow.instance.ts @@ -46,6 +46,7 @@ import { bindContextHookTargets, staticHooksFor } from '../hooks/hooks.utils'; import type ProviderRegistry from '../provider/provider.registry'; import { writeHttpResponse } from '../server/server.validation'; import { flowErrorToHttpOutput } from '../transport/flow-error-output'; +import { deferredFlowLoader } from './deferred-flow'; import { matchMountedPath } from './flow.http-path'; import { cloneStageMap, @@ -56,6 +57,7 @@ import { type StageEntry, type StageMap, } from './flow.stages'; +import { collectFlowMetadata } from './flow.utils'; type StageOutcome = 'ok' | 'respond' | 'next' | 'handled' | 'fail' | 'abort' | 'unknown_error'; @@ -167,6 +169,7 @@ export class FlowInstance extends FlowEntry { readonly globalProviders: ProviderRegistry; private plan: FlowPlan; private FlowClass: FlowType; + private flowClassLoad?: Promise; private stages: StageMap; private hooks: HookRegistry; private readonly logger: FrontMcpLogger; @@ -287,10 +290,29 @@ export class FlowInstance extends FlowEntry { return Promise.resolve(); } + /** Loads the class of a flow registered with `deferredFlow`, and takes its metadata, plan and stages. */ + private loadFlowClass(): Promise { + const load = deferredFlowLoader(this.FlowClass); + if (!load) return Promise.resolve(); + this.flowClassLoad ??= load().then((flowClass) => { + Object.assign(this.metadata, collectFlowMetadata(flowClass)); + this.plan = this.metadata.plan; + this.stages = collectFlowHookMap(flowClass); + this.FlowClass = flowClass; + }); + return this.flowClassLoad; + } + async canActivate(request: ServerRequest): Promise { if (this.method && request.method !== this.method) return false; + try { + await this.loadFlowClass(); + } catch { + // The route is the flow's: its run throws the same error, which the adapter answers with a 500 + return true; + } - const canActivate = this.metadata.middleware?.canActivate ?? []; + const canActivate = [...(this.metadata.middleware?.canActivate ?? [])]; if ((this.FlowClass as any)['canActivate']) { canActivate.push((this.FlowClass as any)['canActivate']); } @@ -383,6 +405,7 @@ export class FlowInstance extends FlowEntry { async run(input: FlowInputOf, deps: Map): Promise | undefined> { this.logger.verbose(`run: starting flow '${this.name}'`); + await this.loadFlowClass(); const scope = this.globalProviders.getActiveScope(); // Build provider views for scoped DI diff --git a/libs/sdk/src/index.ts b/libs/sdk/src/index.ts index 67e67875f..d394692e6 100644 --- a/libs/sdk/src/index.ts +++ b/libs/sdk/src/index.ts @@ -65,8 +65,113 @@ export * from './elicitation'; export * from './task'; export { default as LoggerRegistry } from './logger/logger.registry'; export * from '@frontmcp/guard'; -export * from './remote-mcp'; -export * from './esm-loader'; +// Remote MCP and the ESM loader; the classes and functions of every subsystem in ./subsystems load on first use under CommonJS +export type { + McpClientConnection, + McpConnectionStatus, + McpConnectionInfo, + McpTransportType, + McpHttpTransportOptions, + McpWorkerTransportOptions, + McpEsmTransportOptions, + McpTransportOptions, + McpStaticCredentials, + McpRemoteAuthConfig, + McpRemoteAuthContext, + McpRemoteCapabilities, + McpCapabilityChangeEvent, + McpClientServiceOptions, + McpConnectRequest, + McpRemoteCallToolResult, + McpRemoteReadResourceResult, + McpRemoteGetPromptResult, + McpCapabilityChangeCallback, + McpConnectionChangeCallback, + McpUnsubscribeFn, + CapabilityCacheConfig, + RetryOptions, + CircuitState, + CircuitBreakerOptions, + HealthStatus, + HealthCheckResult, + HealthCheckOptions, +} from './remote-mcp'; +export { + McpClientService, + CapabilityCache, + withRetry, + isTransientError, + isConnectionError, + isAuthError, + CircuitBreaker, + CircuitBreakerManager, + CircuitOpenError, + HealthChecker, + HealthCheckManager, + createRemoteToolContextClass, + createRemoteResourceContextClass, + createRemotePromptContextClass, + buildRemoteToolRecord, + buildRemoteResourceRecord, + buildRemoteResourceTemplateRecord, + buildRemotePromptRecord, + createRemoteToolInstance, + createRemoteResourceInstance, + createRemoteResourceTemplateInstance, + createRemotePromptInstance, +} from './subsystems'; +export type { + ParsedPackageSpecifier, + EsmRegistryAuth, + EsmCacheEntry, + EsmCacheOptions, + FrontMcpPackageManifest, + ManifestPrimitiveKey, + VersionResolutionResult, + VersionResolverOptions, + EsmLoadResult, + EsmModuleLoaderOptions, + VersionPollerOptions, + VersionCheckResult, + EsmToolExecuteHandler, + EsmResourceReadHandler, + EsmPromptExecuteHandler, + EsmToolDefinition, + EsmResourceDefinition, + EsmPromptDefinition, +} from './esm-loader'; +export { + parsePackageSpecifier, + isPackageSpecifier, + buildEsmShUrl, + ESM_SH_BASE_URL, + esmRegistryAuthSchema, + resolveRegistryToken, + getRegistryUrl, + DEFAULT_NPM_REGISTRY, + satisfiesRange, + maxSatisfying, + isValidRange, + isValidVersion, + compareVersions, + isNewerVersion, + EsmCacheManager, + normalizeEsmExport, + frontMcpPackageManifestSchema, + MANIFEST_PRIMITIVE_KEYS, + VersionResolver, + EsmModuleLoader, + VersionPoller, + createEsmToolContextClass, + createEsmResourceContextClass, + createEsmPromptContextClass, + buildEsmToolRecord, + buildEsmResourceRecord, + buildEsmPromptRecord, + createEsmToolInstance, + createEsmResourceInstance, + createEsmPromptInstance, +} from './subsystems'; // Lazy-zod: `z` is lazy by default for cold-start wins, `eagerZ` is the // straight zod pass-through, `lazyZ` is the explicit factory form. @@ -184,7 +289,14 @@ export type { SqliteStorageProviderOptions } from './storage/sqlite-storage.prov export type { OffloadCallOptions, OffloadFunction, OffloadModuleRef } from './offload/offload.types'; // Job exports - saved, discoverable, triggerable executions -export { JobRegistry, JobInstance, JobPermissionGuard, JobExecutionManager, JobEmitter, JobRunResource } from './job'; +export { + JobRegistry, + JobInstance, + JobPermissionGuard, + JobExecutionManager, + JobEmitter, + JobRunResource, +} from './subsystems'; export type { JobRegistryInterface, IndexedJob, @@ -221,10 +333,16 @@ export { ListJobsTool, RegisterJobTool, RemoveJobTool, -} from './job/tools'; +} from './subsystems'; // Workflow exports - managed multi-step job pipelines -export { WorkflowRegistry, WorkflowInstance, WorkflowEngine, WorkflowStepExecutor, WorkflowEmitter } from './workflow'; +export { + WorkflowRegistry, + WorkflowInstance, + WorkflowEngine, + WorkflowStepExecutor, + WorkflowEmitter, +} from './subsystems'; export type { WorkflowRegistryInterface, IndexedWorkflow, @@ -240,7 +358,7 @@ export { ListWorkflowsTool, RegisterWorkflowTool, RemoveWorkflowTool, -} from './workflow/tools'; +} from './subsystems'; // Channel exports - push-based notification channels for Claude Code export { @@ -250,7 +368,7 @@ export { ChannelNotificationService, ChannelEventBus, ChannelReplyTool, -} from './channel'; +} from './subsystems'; export type { ChannelRegistryInterface, IndexedChannel, @@ -636,7 +754,7 @@ export type { McpStatelessClientOptions, McpStatelessInputHandlers } from './tra export { validateHeaderParams, buildParamHeaders } from './transport/mcp-20260728'; export { FRONTMCP_SUPPORTED_PROTOCOL_VERSIONS, TASKS_EXTENSION_ID } from './transport/mcp-20260728'; // Remote-proxy adapter so a FrontMCP server can proxy a 2026-07-28 remote. -export { McpStatelessClientAdapter, negotiateRemoteProtocol } from './remote-mcp/mcp-stateless-client.adapter'; +export { McpStatelessClientAdapter, negotiateRemoteProtocol } from './subsystems'; export { PROTOCOL_2026_07_28 } from '@frontmcp/protocol'; // Web-standard MCP transport helpers — stateless runner + persistent (Durable diff --git a/libs/sdk/src/lazy-subsystems.ts b/libs/sdk/src/lazy-subsystems.ts new file mode 100644 index 000000000..659c9d1e0 --- /dev/null +++ b/libs/sdk/src/lazy-subsystems.ts @@ -0,0 +1,103 @@ +// The CommonJS build's `./subsystems` (see esbuild.cjs.config.js): a subsystem loads the first time one of its exports is read. +{ + const defineLazyExports = (load: () => Record, names: readonly string[]): void => { + let loaded: Record | undefined; + for (const name of names) { + Object.defineProperty(exports, name, { enumerable: true, get: () => (loaded ??= load())[name] }); + } + }; + + defineLazyExports( + () => require('./remote-mcp'), + [ + 'CapabilityCache', + 'CircuitBreaker', + 'CircuitBreakerManager', + 'CircuitOpenError', + 'HealthCheckManager', + 'HealthChecker', + 'McpClientService', + 'buildRemotePromptRecord', + 'buildRemoteResourceRecord', + 'buildRemoteResourceTemplateRecord', + 'buildRemoteToolRecord', + 'createRemotePromptContextClass', + 'createRemotePromptInstance', + 'createRemoteResourceContextClass', + 'createRemoteResourceInstance', + 'createRemoteResourceTemplateInstance', + 'createRemoteToolContextClass', + 'createRemoteToolInstance', + 'isAuthError', + 'isConnectionError', + 'isTransientError', + 'withRetry', + ], + ); + defineLazyExports( + () => require('./remote-mcp/mcp-stateless-client.adapter'), + ['McpStatelessClientAdapter', 'negotiateRemoteProtocol'], + ); + defineLazyExports( + () => require('./esm-loader'), + [ + 'DEFAULT_NPM_REGISTRY', + 'ESM_SH_BASE_URL', + 'EsmCacheManager', + 'EsmModuleLoader', + 'MANIFEST_PRIMITIVE_KEYS', + 'VersionPoller', + 'VersionResolver', + 'buildEsmPromptRecord', + 'buildEsmResourceRecord', + 'buildEsmShUrl', + 'buildEsmToolRecord', + 'compareVersions', + 'createEsmPromptContextClass', + 'createEsmPromptInstance', + 'createEsmResourceContextClass', + 'createEsmResourceInstance', + 'createEsmToolContextClass', + 'createEsmToolInstance', + 'esmRegistryAuthSchema', + 'frontMcpPackageManifestSchema', + 'getRegistryUrl', + 'isNewerVersion', + 'isPackageSpecifier', + 'isValidRange', + 'isValidVersion', + 'maxSatisfying', + 'normalizeEsmExport', + 'parsePackageSpecifier', + 'resolveRegistryToken', + 'satisfiesRange', + ], + ); + defineLazyExports( + () => require('./job'), + ['JobRegistry', 'JobInstance', 'JobPermissionGuard', 'JobExecutionManager', 'JobEmitter', 'JobRunResource'], + ); + defineLazyExports( + () => require('./job/tools'), + ['ExecuteJobTool', 'GetJobStatusTool', 'CancelJobTool', 'ListJobsTool', 'RegisterJobTool', 'RemoveJobTool'], + ); + defineLazyExports( + () => require('./workflow'), + ['WorkflowRegistry', 'WorkflowInstance', 'WorkflowEngine', 'WorkflowStepExecutor', 'WorkflowEmitter'], + ); + defineLazyExports( + () => require('./workflow/tools'), + ['ExecuteWorkflowTool', 'GetWorkflowStatusTool', 'ListWorkflowsTool', 'RegisterWorkflowTool', 'RemoveWorkflowTool'], + ); + defineLazyExports( + () => require('./channel'), + [ + 'ChannelInstance', + 'ChannelRegistry', + 'ChannelEmitter', + 'ChannelNotificationService', + 'ChannelEventBus', + 'ChannelReplyTool', + ], + ); +} diff --git a/libs/sdk/src/subsystems.ts b/libs/sdk/src/subsystems.ts new file mode 100644 index 000000000..f8eedce9b --- /dev/null +++ b/libs/sdk/src/subsystems.ts @@ -0,0 +1,29 @@ +// The root exports of the subsystems a server may never use. The CommonJS build takes ./lazy-subsystems instead. +export * from './remote-mcp'; +export * from './esm-loader'; +export { McpStatelessClientAdapter, negotiateRemoteProtocol } from './remote-mcp/mcp-stateless-client.adapter'; +export { JobRegistry, JobInstance, JobPermissionGuard, JobExecutionManager, JobEmitter, JobRunResource } from './job'; +export { + ExecuteJobTool, + GetJobStatusTool, + CancelJobTool, + ListJobsTool, + RegisterJobTool, + RemoveJobTool, +} from './job/tools'; +export { WorkflowRegistry, WorkflowInstance, WorkflowEngine, WorkflowStepExecutor, WorkflowEmitter } from './workflow'; +export { + ExecuteWorkflowTool, + GetWorkflowStatusTool, + ListWorkflowsTool, + RegisterWorkflowTool, + RemoveWorkflowTool, +} from './workflow/tools'; +export { + ChannelInstance, + ChannelRegistry, + ChannelEmitter, + ChannelNotificationService, + ChannelEventBus, + ChannelReplyTool, +} from './channel';