diff --git a/transports/bifrost-http/lib/config.go b/transports/bifrost-http/lib/config.go index ca591c2074..67a1344a20 100644 --- a/transports/bifrost-http/lib/config.go +++ b/transports/bifrost-http/lib/config.go @@ -5131,13 +5131,17 @@ func (c *Config) rebuildInterfaceCaches() { var httpTransport []schemas.HTTPTransportPlugin for _, p := range *basePlugins { - if llmPlugin, ok := p.(schemas.LLMPlugin); ok { + // Use the framework's As* helpers rather than bare type assertions: a .so plugin + // is wrapped in a DynamicPlugin that satisfies every hook interface even when the + // .so exports none of those symbols, so bare assertions put every dynamic plugin + // into all three caches and invoke no-op hooks on every request. + if llmPlugin := plugins.AsLLMPlugin(p); llmPlugin != nil { llm = append(llm, llmPlugin) } - if mcpPlugin, ok := p.(schemas.MCPPlugin); ok { + if mcpPlugin := plugins.AsMCPPlugin(p); mcpPlugin != nil { mcp = append(mcp, mcpPlugin) } - if httpPlugin, ok := p.(schemas.HTTPTransportPlugin); ok { + if httpPlugin := plugins.AsHTTPTransportPlugin(p); httpPlugin != nil { httpTransport = append(httpTransport, httpPlugin) } if cm, ok := p.(schemas.ConfigMarshallerPlugin); ok { diff --git a/transports/bifrost-http/server/plugins.go b/transports/bifrost-http/server/plugins.go index 20af9bfbb9..c3b9a8bf1f 100644 --- a/transports/bifrost-http/server/plugins.go +++ b/transports/bifrost-http/server/plugins.go @@ -7,6 +7,7 @@ import ( "slices" "github.com/maximhq/bifrost/core/schemas" + dynamicPlugins "github.com/maximhq/bifrost/framework/plugins" "github.com/maximhq/bifrost/plugins/compat" "github.com/maximhq/bifrost/plugins/governance" "github.com/maximhq/bifrost/plugins/logging" @@ -20,16 +21,22 @@ import ( "github.com/maximhq/bifrost/transports/bifrost-http/lib" ) -// InferPluginTypes determines which interface types a plugin implements +// InferPluginTypes determines which interface types a plugin implements. +// A .so plugin is wrapped in a DynamicPlugin, which structurally satisfies every hook +// interface regardless of which symbols the .so actually exports, so a plain type +// assertion reports all three types for every dynamic plugin. That made RemovePlugin +// remove the same instance from both the LLM and MCP lists and call Cleanup() once per +// list. The framework's As* helpers additionally check the hook function pointers, so +// dynamic plugins are classified by the symbols they really export. func InferPluginTypes(plugin schemas.BasePlugin) []schemas.PluginType { var types []schemas.PluginType - if _, ok := plugin.(schemas.LLMPlugin); ok { + if dynamicPlugins.AsLLMPlugin(plugin) != nil { types = append(types, schemas.PluginTypeLLM) } - if _, ok := plugin.(schemas.MCPPlugin); ok { + if dynamicPlugins.AsMCPPlugin(plugin) != nil { types = append(types, schemas.PluginTypeMCP) } - if _, ok := plugin.(schemas.HTTPTransportPlugin); ok { + if dynamicPlugins.AsHTTPTransportPlugin(plugin) != nil { types = append(types, schemas.PluginTypeHTTP) } return types