diff --git a/packages/plugin-sdk/package.json b/packages/plugin-sdk/package.json index b73cc1490..bc99c052b 100644 --- a/packages/plugin-sdk/package.json +++ b/packages/plugin-sdk/package.json @@ -18,6 +18,11 @@ ".": { "types": "./dist/index.d.mts", "default": "./dist/index.mjs" + }, + "./plugin-host": { + "types": "./dist/plugin-host/index.d.mts", + "node": "./dist/plugin-host/runtimes/node/index.mjs", + "default": "./dist/plugin-host/runtimes/web/index.mjs" } }, "main": "./dist/index.mjs", diff --git a/packages/plugin-sdk/src/channels/shared.ts b/packages/plugin-sdk/src/channels/shared.ts index ea1e8d969..0fd8783a0 100644 --- a/packages/plugin-sdk/src/channels/shared.ts +++ b/packages/plugin-sdk/src/channels/shared.ts @@ -1,3 +1,3 @@ import type { createContext } from '@moeru/eventa' -export type ChannelControlPlane = ReturnType +export type ChannelHost = ReturnType diff --git a/packages/plugin-sdk/src/plugin-host/index.test.ts b/packages/plugin-sdk/src/plugin-host/core.test.ts similarity index 81% rename from packages/plugin-sdk/src/plugin-host/index.test.ts rename to packages/plugin-sdk/src/plugin-host/core.test.ts index d0b7c3486..d62ef0387 100644 --- a/packages/plugin-sdk/src/plugin-host/index.test.ts +++ b/packages/plugin-sdk/src/plugin-host/core.test.ts @@ -4,7 +4,7 @@ import { createContext, defineEventa, defineInvokeHandler } from '@moeru/eventa' import { describe, expect, it, vi } from 'vitest' import { FileSystemLoader } from '.' -import { channels } from '../channels' +import { createApis } from '../plugin/apis/client' import { protocolProviders } from '../plugin/apis/protocol' describe('for FileSystemPluginHost', () => { @@ -21,10 +21,11 @@ describe('for FileSystemPluginHost', () => { }, { cwd: '' }) const ctx = createContext() + const apis = createApis(ctx) const onVitestCall = vi.fn() ctx.on(defineEventa('vitest-call:init'), onVitestCall) - await expect(pluginDef.init({ host: ctx })).resolves.not.toThrow() + await expect(pluginDef.init?.({ channels: { host: ctx }, apis })).resolves.not.toThrow() expect(onVitestCall).toHaveBeenCalledTimes(1) }) @@ -56,13 +57,14 @@ describe('for PluginHost', () => { }, { cwd: '' }) const ctx = createContext() + const apis = createApis(ctx) const onVitestCall = vi.fn() ctx.on(defineEventa('vitest-call:init'), onVitestCall) - await expect(pluginDef.init({ host: ctx })).resolves.not.toThrow() + await expect(pluginDef.init?.({ channels: { host: ctx }, apis })).resolves.not.toThrow() expect(onVitestCall).toHaveBeenCalledTimes(1) - defineInvokeHandler(channels.data, protocolProviders.listProviders, async () => { + defineInvokeHandler(ctx, protocolProviders.listProviders, async () => { return [ { name: 'provider1' }, ] @@ -70,7 +72,7 @@ describe('for PluginHost', () => { const onProviderListCall = vi.fn() ctx.on(protocolProviders.listProviders.sendEvent, onProviderListCall) - await expect(pluginDef.setupModules?.()).resolves.not.toThrow() + await expect(pluginDef.setupModules?.({ channels: { host: ctx }, apis })).resolves.not.toThrow() expect(onProviderListCall).toHaveBeenCalledTimes(1) }) }) diff --git a/packages/plugin-sdk/src/plugin-host/core.ts b/packages/plugin-sdk/src/plugin-host/core.ts new file mode 100644 index 000000000..5f7821a50 --- /dev/null +++ b/packages/plugin-sdk/src/plugin-host/core.ts @@ -0,0 +1,169 @@ +import type { definePlugin } from '../plugin' +import type { Plugin } from '../plugin/shared' + +import { join } from 'node:path' +import { cwd } from 'node:process' + +/** + * Plugin Host lifecycle overview (transport-aware): + * + * - The host loads a plugin entrypoint (local or remote). + * - The host resolves a per-plugin transport (in-memory, worker, WebSocket, electron). + * - The host creates an Eventa context bound to that transport. + * - The host binds SDK APIs to the context and passes them into plugin.init. + * + * This design allows multiple plugins in one host without shared global channels. + * Each plugin instance has its own context and transport, so local and remote + * plugins share the same API surface while remaining isolated. + */ +/** + * One plugin could contribute multiple modules. + * + * For plugin itself, there are two ways to implement it, either local plugin, or remote plugin. + * Since we have @moeru/eventa as underlying event transmission, we can drive everything in event. + * + * It's ok that local plugin doesn't implement the remote protocol to handle the remote plugin + * RPC if doesn't wish for. Purely local UI manipulation or local resource registration is normal. + * + * In another word, we could implement the plugin in same eventa definition, while switching + * between two different transport. + * + * For local plugin, local context for in-memory transport will be used. + * For remote plugin, server-runtime for WebSocket based transport will be used. + * + * + * The procedure looks like this (regardless to the underlying transport since we will implement + * in both): + * + * 0. Channel Gateway sits on top of all channels + * 1. Connect to control plane channel (from plugin-sdk, or any language implementation will impl) + * 2. Authenticate with module:authenticate + * 3. Negotiate protocol/api compatibility before lifecycle work starts: + * 1. Plugin sends module:compatibility:request with: + * - plugin protocol version + * - plugin sdk api version + * - optional supported ranges for backward/forward compatibility + * 2. Plugin Host replies module:compatibility:result with: + * - accepted version tuple (protocol + api) + * - compatibility mode (exact, downgraded, rejected) + * - deterministic reason if rejected + * 3. If rejected, host MUST stop initialization for that plugin and emit module:status + * with incompatible-version details for Configurator visibility. + * 4. Plugin Host will send registry:modules:sync, this ensures the auto plugin / dependency discovery + * 5. Module will now announce itself to the entire system through module:announce + * 6. Module will now sync to Plugin Host that module now preparing, declaring its: + * 1. Dependencies to other plugins / modules + * 2. Initial Configuration (doesn't relate to capabilities) + * Note that for capabilities requires Database configuration, and perhaps Memory manipulation, + * plugin should orchestrate itself to contribute many capabilities / features, and the needed + * configurations and credentials should be requested and configured for each capabilities + * instead. + * 7. During this phase, if module failed to find the needed dependency, module:status will be emitted + * to allow the Plugin Host to surface errors or notice up to Configurator layer, to display the + * needed warning and status. + * + * It's ok for module to stay online / connected to channels. In this phase, module:announce + * could happen multiple times. Module is ok to listen to the sync events and decide whether to enter + * the next phases if needed. + * 8. During this phase, if plugin successfully configured itself and calculated / computed the possible + * contributing capabilities / features, it will emit module:prepared. + * 9. During this phase, if module requires more configuration to fill and enable in order to go next + * phase, it's ok, it will emit module:configuration:needed. + * 10. Module should now emit module:prepared. + * 11. Module should now emit module:configuration:needed, for telling the shape to Configurator. + * In between, for user side / Configurator side: + * - module:configuration:validate:request (static check, zod/valibot or programmatic checks) + * - module:configuration:validate:status (with parent event id) + * - module:configuration:validate:response + * - module:configuration:plan:request (actually dry-run, ensures anything during runtime works) + * - module:configuration:plan:status (with parent event id) + * - module:configuration:plan:response + * - module:configuration:commit + * - module:configuration:commit:status (with parent event id) + * 12. Module previously configured will get validate, plan, and commit automatically, if failed, status + * will surface to the Configurator side for further noticing to user. + * 13. Module should now emit module:configuration:configured. + * 14. Module should now be able to calculate / compute possible capabilities / features to be able to + * contribute to the system / Plugin Host, once calculated, module:contribute:capability:offer will + * be emitted in (length of) capabilities times. + * + * This means for 1 module that offers 5 capabilities, 5 * module:contribute:capability:offer will + * be emitted. + * 15. Next, module will now enter the capability / feature fill-in phase, during this phase, it's ok + * to say that the plugin is running but nothing gets contributed if none of them were configured. + * + * For any capabilities without further configuration and fill-in from Configurator and User side, + * it can be automatically activated now (which is next phase for module:contribute:capability:* + * events), module:contribute:capability:configuration:configured, + * module:contribute:capability:activated will be emitted. + * + * If further configuration and actions needed, module:contribute:capability:configuration:needed + * will be emitted. + * + * To configure the capabilities in sequence and correct order, + * - module:contribute:capability:configuration:validate:request (static check, zod/valibot or programmatic checks) + * - module:contribute:capability:configuration:validate:status (with parent event id) + * - module:contribute:capability:configuration:validate:response + * - module:contribute:capability:configuration:plan:request (actually dry-run, ensures anything during runtime works) + * - module:contribute:capability:configuration:plan:status (with parent event id) + * - module:contribute:capability:configuration:plan:response + * - module:contribute:capability:configuration:commit + * - module:contribute:capability:configuration:commit:status (with parent event id) + * similar to module:configuration are accepted. + * + * 16. No matter what happens, the module:status should emit with ready status now. + * 17. Any time the module need to re-calculate / re-compute, or wish to be re-configured, it's ok to + * emit module:status:change with needed phase to update, if need to rollback to announced phase, + * Plugin Host should treat the Module to be un-prepared status, the needed procedure will be called. + */ + +export class PluginHost { + constructor() { + + } +} + +export interface ManifestV1 { + apiVersion: 'v1' + kind: 'manifest.plugin.airi.moeru.ai' + name: string + entrypoints: { + electron?: string + } +} + +export class FileSystemLoader { + constructor() { + + } + + async loadLazyPluginFor(manifest: ManifestV1, options?: { cwd?: string }) { + const root = options?.cwd ?? cwd() + if (!manifest.entrypoints.electron) { + throw new Error('' + + 'For locally installed, defined plugin, electron entrypoint is required.' + + 'The value of `entrypoints.electron` should be the relative path to the ' + + 'root of app.getPath(\'userData\').', + ) + } + + const entrypoint = join(root, manifest.entrypoints.electron) + const pluginModule = await import(entrypoint) as { default: ReturnType } + return pluginModule.default + } + + async loadPluginFor(manifest: ManifestV1, options?: { cwd?: string }) { + const root = options?.cwd ?? cwd() + if (!manifest.entrypoints.electron) { + throw new Error('' + + 'For locally installed, defined plugin, electron entrypoint is required.' + + 'The value of `entrypoints.electron` should be the relative path to the ' + + 'root of app.getPath(\'userData\').', + ) + } + + const entrypoint = join(root, manifest.entrypoints.electron) + const pluginModule = await import(entrypoint) as Plugin + return pluginModule + } +} diff --git a/packages/plugin-sdk/src/plugin-host/index.ts b/packages/plugin-sdk/src/plugin-host/index.ts index 5f6781afd..08a08e28b 100644 --- a/packages/plugin-sdk/src/plugin-host/index.ts +++ b/packages/plugin-sdk/src/plugin-host/index.ts @@ -1,158 +1,3 @@ -import type { definePlugin } from '../plugin' -import type { Plugin } from '../plugin/shared' - -import { join } from 'node:path' -import { cwd } from 'node:process' - -/** - * Plugin Host lifecycle overview (transport-aware): - * - * - The host loads a plugin entrypoint (local or remote). - * - The host resolves a per-plugin transport (in-memory, worker, WebSocket, electron). - * - The host creates an Eventa context bound to that transport. - * - The host binds SDK APIs to the context and passes them into plugin.init. - * - * This design allows multiple plugins in one host without shared global channels. - * Each plugin instance has its own context and transport, so local and remote - * plugins share the same API surface while remaining isolated. - */ -/** - * One plugin could contribute multiple modules. - * - * For plugin itself, there are two ways to implement it, either local plugin, or remote plugin. - * Since we have @moeru/eventa as underlying event transmission, we can drive everything in event. - * - * It's ok that local plugin doesn't implement the remote protocol to handle the remote plugin - * RPC if doesn't wish for. Purely local UI manipulation or local resource registration is normal. - * - * In another word, we could implement the plugin in same eventa definition, while switching - * between two different transport. - * - * For local plugin, local context for in-memory transport will be used. - * For remote plugin, server-runtime for WebSocket based transport will be used. - * - * - * The procedure looks like this (regardless to the underlying transport since we will implement - * in both): - * - * 0. Channel Gateway sits on top of all channels - * 1. Connect to control plane channel (from plugin-sdk, or any language implementation will impl) - * 2. Authenticate with module:authenticate - * 3. Plugin Host will send registry:modules:sync, this ensures the auto plugin / dependency discovery - * 4. Module will now announce itself to the entire system through module:announce - * 5. Module will now sync to Plugin Host that module now preparing, declaring its: - * 1. Dependencies to other plugins / modules - * 2. Initial Configuration (doesn't relate to capabilities) - * Note that for capabilities requires Database configuration, and perhaps Memory manipulation, - * plugin should orchestrate itself to contribute many capabilities / features, and the needed - * configurations and credentials should be requested and configured for each capabilities - * instead. - * 6. During this phase, if module failed to find the needed dependency, module:status will be emitted - * to allow the Plugin Host to surface errors or notice up to Configurator layer, to display the - * needed warning and status. - * - * It's ok for module to stay online / connected to channels. In this phase, module:announce - * could happen multiple times. Module is ok to listen to the sync events and decide whether to enter - * the next phases if needed. - * 7. During this phase, if plugin successfully configured itself and calculated / computed the possible - * contributing capabilities / features, it will emit module:prepared. - * 8. During this phase, if module requires more configuration to fill and enable in order to go next - * phase, it's ok, it will emit module:configuration:needed. - * 8. Module should now emit module:prepared. - * 9. Module should now emit module:configuration:needed, for telling the shape to Configurator. - * In between, for user side / Configurator side: - * - module:configuration:validate:request (static check, zod/valibot or programmatic checks) - * - module:configuration:validate:status (with parent event id) - * - module:configuration:validate:response - * - module:configuration:plan:request (actually dry-run, ensures anything during runtime works) - * - module:configuration:plan:status (with parent event id) - * - module:configuration:plan:response - * - module:configuration:commit - * - module:configuration:commit:status (with parent event id) - * 9. Module previously configured will get validate, plan, and commit automatically, if failed, status - * will surface to the Configurator side for further noticing to user. - * 10. Module should now emit module:configuration:configured. - * 11. Module should now be able to calculate / compute possible capabilities / features to be able to - * contribute to the system / Plugin Host, once calculated, module:contribute:capability:offer will - * be emitted in (length of) capabilities times. - * - * This means for 1 module that offers 5 capabilities, 5 * module:contribute:capability:offer will - * be emitted. - * 12. Next, module will now enter the capability / feature fill-in phase, during this phase, it's ok - * to say that the plugin is running but nothing gets contributed if none of them were configured. - * - * For any capabilities without further configuration and fill-in from Configurator and User side, - * it can be automatically activated now (which is next phase for module:contribute:capability:* - * events), module:contribute:capability:configuration:configured, - * module:contribute:capability:activated will be emitted. - * - * If further configuration and actions needed, module:contribute:capability:configuration:needed - * will be emitted. - * - * To configure the capabilities in sequence and correct order, - * - module:contribute:capability:configuration:validate:request (static check, zod/valibot or programmatic checks) - * - module:contribute:capability:configuration:validate:status (with parent event id) - * - module:contribute:capability:configuration:validate:response - * - module:contribute:capability:configuration:plan:request (actually dry-run, ensures anything during runtime works) - * - module:contribute:capability:configuration:plan:status (with parent event id) - * - module:contribute:capability:configuration:plan:response - * - module:contribute:capability:configuration:commit - * - module:contribute:capability:configuration:commit:status (with parent event id) - * similar to module:configuration are accepted. - * - * 13. No matter what happens, the module:status should emit with ready status now. - * 14. Any time the module need to re-calculate / re-compute, or wish to be re-configured, it's ok to - * emit module:status:change with needed phase to update, if need to rollback to announced phase, - * Plugin Host should treat the Module to be un-prepared status, the needed procedure will be called. - */ - -export class PluginHost { - constructor() { - - } -} - -export interface ManifestV1 { - apiVersion: 'v1' - kind: 'manifest.plugin.airi.moeru.ai' - name: string - entrypoints: { - electron?: string - } -} - -export class FileSystemLoader { - constructor() { - - } - - async loadLazyPluginFor(manifest: ManifestV1, options?: { cwd?: string }) { - const root = options?.cwd ?? cwd() - if (!manifest.entrypoints.electron) { - throw new Error('' - + 'For locally installed, defined plugin, electron entrypoint is required.' - + 'The value of `entrypoints.electron` should be the relative path to the ' - + 'root of app.getPath(\'userData\').', - ) - } - - const entrypoint = join(root, manifest.entrypoints.electron) - const pluginModule = await import(entrypoint) as { default: ReturnType } - return pluginModule.default - } - - async loadPluginFor(manifest: ManifestV1, options?: { cwd?: string }) { - const root = options?.cwd ?? cwd() - if (!manifest.entrypoints.electron) { - throw new Error('' - + 'For locally installed, defined plugin, electron entrypoint is required.' - + 'The value of `entrypoints.electron` should be the relative path to the ' - + 'root of app.getPath(\'userData\').', - ) - } - - const entrypoint = join(root, manifest.entrypoints.electron) - const pluginModule = await import(entrypoint) as Plugin - return pluginModule - } -} +export * from './core' +export { createPluginContext } from './runtimes/node' +export * from './transports' diff --git a/packages/plugin-sdk/src/plugin-host/runtimes/node/index.ts b/packages/plugin-sdk/src/plugin-host/runtimes/node/index.ts new file mode 100644 index 000000000..90dd8d29c --- /dev/null +++ b/packages/plugin-sdk/src/plugin-host/runtimes/node/index.ts @@ -0,0 +1,25 @@ +import type { EventContext } from '@moeru/eventa' + +import type { PluginTransport } from '../../transports' + +import { createContext } from '@moeru/eventa' + +export * from '../../core' +export * from '../../transports' + +export function createPluginContext(transport: PluginTransport): EventContext { + switch (transport.kind) { + case 'in-memory': + return createContext() + case 'websocket': + throw new Error('WebSocket transport is not implemented for node runtime yet.') + case 'node-worker': + throw new Error('Node worker transport is not implemented yet.') + case 'electron': + throw new Error('Electron transport is not implemented yet.') + case 'web-worker': + throw new Error('Web worker transport is not available in node runtime.') + default: + throw new Error('Unknown plugin transport kind.') + } +} diff --git a/packages/plugin-sdk/src/plugin-host/runtimes/web/index.ts b/packages/plugin-sdk/src/plugin-host/runtimes/web/index.ts new file mode 100644 index 000000000..5b144d552 --- /dev/null +++ b/packages/plugin-sdk/src/plugin-host/runtimes/web/index.ts @@ -0,0 +1,25 @@ +import type { EventContext } from '@moeru/eventa' + +import type { PluginTransport } from '../../transports' + +import { createContext } from '@moeru/eventa' + +export * from '../../core' +export * from '../../transports' + +export function createPluginContext(transport: PluginTransport): EventContext { + switch (transport.kind) { + case 'in-memory': + return createContext() + case 'websocket': + throw new Error('WebSocket transport is not implemented for web runtime yet.') + case 'web-worker': + throw new Error('Web worker transport is not implemented yet.') + case 'node-worker': + throw new Error('Node worker transport is not available in web runtime.') + case 'electron': + throw new Error('Electron transport is not available in web runtime.') + default: + throw new Error('Unknown plugin transport kind.') + } +} diff --git a/packages/plugin-sdk/src/plugin-host/testdata/test-normal-plugin.ts b/packages/plugin-sdk/src/plugin-host/testdata/test-normal-plugin.ts index d8e8d311d..784e4126c 100644 --- a/packages/plugin-sdk/src/plugin-host/testdata/test-normal-plugin.ts +++ b/packages/plugin-sdk/src/plugin-host/testdata/test-normal-plugin.ts @@ -2,16 +2,15 @@ import type { ContextInit } from '../../plugin/shared' import { defineEventa } from '@moeru/eventa' -import { channels, providers } from '../../plugin' - -export async function init(initContext: ContextInit): Promise { - initContext.host.emit(defineEventa('vitest-call:init'), undefined) +export async function init({ channels }: ContextInit): Promise { + channels.host.emit(defineEventa('vitest-call:init'), undefined) } export async function configure(): Promise { } -export async function setupModules(): Promise { - channels.host.emit(defineEventa('vitest-call:setup-modules'), await providers.listProviders()) +export async function setupModules({ apis, channels }: ContextInit): Promise { + const providerList = await apis.providers.listProviders() + channels.host.emit(defineEventa('vitest-call:setup-modules'), providerList) } diff --git a/packages/plugin-sdk/src/plugin-host/transports/index.ts b/packages/plugin-sdk/src/plugin-host/transports/index.ts new file mode 100644 index 000000000..33981d1cc --- /dev/null +++ b/packages/plugin-sdk/src/plugin-host/transports/index.ts @@ -0,0 +1,6 @@ +export type PluginTransport + = | { kind: 'in-memory' } + | { kind: 'websocket', url: string, protocols?: string[] } + | { kind: 'web-worker', worker: Worker } + | { kind: 'node-worker', worker: import('node:worker_threads').Worker } + | { kind: 'electron', target: 'main' | 'renderer', webContentsId?: number } diff --git a/packages/plugin-sdk/src/plugin/apis/client/index.ts b/packages/plugin-sdk/src/plugin/apis/client/index.ts index 8e6165b61..49867731f 100644 --- a/packages/plugin-sdk/src/plugin/apis/client/index.ts +++ b/packages/plugin-sdk/src/plugin/apis/client/index.ts @@ -1,2 +1,12 @@ -export { channels } from '../../../channels' +import type { EventContext } from '@moeru/eventa' + +import { createProviders } from './resources' + +export function createApis(ctx: EventContext) { + return { + providers: createProviders(ctx), + } +} + +export type PluginApis = ReturnType export * from './resources' diff --git a/packages/plugin-sdk/src/plugin/apis/client/resources/index.ts b/packages/plugin-sdk/src/plugin/apis/client/resources/index.ts index f5e7d6479..22a649944 100644 --- a/packages/plugin-sdk/src/plugin/apis/client/resources/index.ts +++ b/packages/plugin-sdk/src/plugin/apis/client/resources/index.ts @@ -1 +1 @@ -export { providers } from './providers' +export { createProviders } from './providers' diff --git a/packages/plugin-sdk/src/plugin/apis/client/resources/providers/index.ts b/packages/plugin-sdk/src/plugin/apis/client/resources/providers/index.ts index 6588a1a76..9c2ead17a 100644 --- a/packages/plugin-sdk/src/plugin/apis/client/resources/providers/index.ts +++ b/packages/plugin-sdk/src/plugin/apis/client/resources/providers/index.ts @@ -1,13 +1,14 @@ +import type { EventContext } from '@moeru/eventa' + import { defineInvoke } from '@moeru/eventa' -import { channels } from '../../../../../channels' import { protocolListProviders } from '../../../protocol/resources/providers' -export async function listProviders() { - const func = defineInvoke(channels.data, protocolListProviders) - return func() -} - -export const providers = { - listProviders, +export function createProviders(ctx: EventContext) { + return { + listProviders() { + const func = defineInvoke(ctx, protocolListProviders) + return func() + }, + } } diff --git a/packages/plugin-sdk/src/plugin/shared.ts b/packages/plugin-sdk/src/plugin/shared.ts index 544e727e7..3959149b2 100644 --- a/packages/plugin-sdk/src/plugin/shared.ts +++ b/packages/plugin-sdk/src/plugin/shared.ts @@ -1,7 +1,11 @@ -import type { ChannelControlPlane } from '../channels/shared' +import type { ChannelHost } from '../channels/shared' +import type { PluginApis } from './apis/client' export interface ContextInit { - host: ChannelControlPlane + channels: { + host: ChannelHost + } + apis: PluginApis } export interface Plugin { @@ -12,5 +16,5 @@ export interface Plugin { /** * */ - setupModules?: () => Promise + setupModules?: (initContext: ContextInit) => Promise } diff --git a/packages/plugin-sdk/tsconfig.json b/packages/plugin-sdk/tsconfig.json index 00dcfd807..6869a6239 100644 --- a/packages/plugin-sdk/tsconfig.json +++ b/packages/plugin-sdk/tsconfig.json @@ -2,7 +2,8 @@ "compilerOptions": { "target": "ESNext", "lib": [ - "ESNext" + "ESNext", + "DOM" ], "module": "ESNext", "moduleResolution": "bundler", diff --git a/packages/plugin-sdk/tsdown.config.ts b/packages/plugin-sdk/tsdown.config.ts index 5d4db3d25..95419c139 100644 --- a/packages/plugin-sdk/tsdown.config.ts +++ b/packages/plugin-sdk/tsdown.config.ts @@ -3,6 +3,9 @@ import { defineConfig } from 'tsdown' export default defineConfig({ entry: [ 'src/index.ts', + 'src/plugin-host/index.ts', + 'src/plugin-host/runtimes/node/index.ts', + 'src/plugin-host/runtimes/web/index.ts', ], dts: true, format: 'esm',