Skip to content

Conversation

@akshaydeo
Copy link
Contributor

@akshaydeo akshaydeo commented Nov 19, 2025

Summary

Replace the TransportInterceptor plugin interface with a more flexible HTTPTransportMiddleware approach that follows the standard middleware pattern used in HTTP servers.

Changes

  • Replaced TransportInterceptor method in the Plugin interface with HTTPTransportMiddleware that returns a middleware function
  • Added a new BifrostHTTPMiddleware type in the core schemas package
  • Updated all plugins to implement the new middleware interface
  • Modified the HTTP transport layer to use the new middleware pattern
  • Updated the middleware chain implementation to work with the new approach
  • Refactored the governance plugin to use the new middleware pattern for request interception

Type of change

  • Bug fix
  • Feature
  • Refactor
  • Documentation
  • Chore/CI

Affected areas

  • Core (Go)
  • Transports (HTTP)
  • Providers/Integrations
  • Plugins
  • UI (Next.js)
  • Docs

How to test

Test that plugins can still intercept and modify HTTP requests:

# Core/Transports
go version
go test ./...

# Test with a custom plugin that implements HTTPTransportMiddleware
cd examples/plugins/hello-world
go build -buildmode=plugin -o hello-world.so .

Then configure Bifrost to use this plugin and verify that the middleware is called during HTTP requests.

Breaking changes

  • Yes
  • No

This is a breaking change for plugin developers. Any custom plugins that implement the TransportInterceptor method will need to be updated to implement the new HTTPTransportMiddleware method instead.

Migration instructions:

  1. Replace TransportInterceptor with HTTPTransportMiddleware in your plugin
  2. Update the function signature to return a middleware function
  3. Move your interception logic into the middleware function

Security considerations

The new middleware pattern provides better isolation between plugins and more control over the request/response lifecycle, which could improve security by reducing the risk of plugins interfering with each other.

Checklist

  • I added/updated tests where appropriate
  • I verified builds succeed (Go and UI)

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 19, 2025

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Distributed tracing with end-to-end traces/spans and observability plugin support
    • Development profiler dashboard showing real-time and historical metrics
  • Breaking Changes

    • Plugin transport hook replaced by middleware-based HTTP transport (migration required for plugins)
  • Improvements

    • Central streaming accumulator reduces memory usage
    • Time-to-first-token (TTFT) metrics for streaming
    • Minor UI refinements (sidebar, switches, sheets) and layout tweaks
  • Documentation

    • Plugin migration guide and updated plugin docs for v1.4.x+

✏️ Tip: You can customize this high-level summary in your review settings.

Walkthrough

This PR replaces the Plugin TransportInterceptor hook with an HTTPTransportMiddleware (fasthttp middleware), adds a tracing/observability subsystem (Trace/Span models, Tracer, TraceStore, propagation, helpers), threads tracing through core request and streaming flows (TTFT), updates many handlers/plugins to the new middleware API, and adds a dev pprof profiler and UI integration.

Changes

Cohort / File(s) Summary
Core plugin & tracing types
core/schemas/plugin.go, core/schemas/trace.go, core/schemas/tracer.go, core/schemas/bifrost.go, core/schemas/context.go, core/changelog.md, core/version
Adds BifrostHTTPMiddleware type, replaces TransportInterceptor with HTTPTransportMiddleware() on Plugin; adds ObservabilityPlugin (Inject). Introduces Trace/Span model, Tracer interface, tracer keys, and version bump to 1.3.0.
Core runtime integration
core/bifrost.go, core/bifrost_test.go, core/providers/utils/utils.go, core/mcp.go, core/schemas/*
Wires tracer into request lifecycle, adds SetTracer, streaming finalization APIs, deferred-span completion, and TTFT-aware streaming handling; test callsite updates.
Framework tracing & store
framework/tracing/{store,tracer,helpers,llmspan,propagation}.go, framework/tracing/helpers.go
New TraceStore, Tracer implementation, helpers for spans/traces, W3C propagation, LLM attribute population and utilities.
Streaming accumulator and types
framework/streaming/{types,accumulator,chat,responses,transcription,audio}.go
Adds FirstChunkTimestamp and TimeToFirstToken; initializes start timestamps and computes TTFT for streaming accumulation.
Transport middleware & handlers
transports/bifrost-http/handlers/middlewares.go, transports/bifrost-http/lib/middleware.go, transports/bifrost-http/handlers/*.go, transports/bifrost-http/integrations/router.go, transports/bifrost-http/server/server.go, transports/bifrost-http/lib/config.go
Major refactor: middleware signatures now use schemas.BifrostHTTPMiddleware; introduces TracingMiddleware, removes internal TransportInterceptor logic, updates RegisterRoutes signatures across many handlers, removes EnterpriseOverrides from config/plugin loading, and wires tracing into server bootstrap.
Dynamic plugin loader & examples
framework/plugins/dynamicplugin.go, framework/plugins/dynamicplugin_test.go, examples/plugins/hello-world/*
Dynamic plugin symbol lookup changed to HTTPTransportMiddleware; tests and example plugin updated to fasthttp middleware signature; go.mod updates for fasthttp.
Plugins: implementations & observability
plugins/{governance,logging,otel,jsonparser,maxim,mocker,semanticcache,telemetry}/*, changelogs
Plugins updated to implement HTTPTransportMiddleware() (many return nil), logging/otel refactors integrate central tracer/Inject, otel adds Inject(trace) and OTEL conversion; many changelogs updated.
OTEL conversion & cleanup
plugins/otel/converter.go, plugins/otel/ttlsyncmap.go
Centralized trace→OTEL conversion helpers added; TTL sync map removed (replaced by trace store).
Dev profiler & UI
transports/bifrost-http/handlers/{devpprof,devpprof_unix,devpprof_windows}.go, ui/components/devProfiler.tsx, ui/lib/store/apis/devApi.ts, ui/app/clientLayout.tsx
Adds /api/dev/pprof handler, platform CPU sampling, DevProfiler React component + RTK Query slice, and integrates profiler into client layout.
Middleware tests & lib
transports/bifrost-http/handlers/middlewares_test.go, transports/bifrost-http/lib/middleware.go
Tests updated to new middleware type; ChainMiddlewares now accepts schemas.BifrostHTTPMiddleware.
Logging, streaming response adapters
plugins/logging/utils.go, core/providers/utils/utils.go
Logging adapts stream accumulator results to processed responses; response/error flows finalize deferred spans on stream end.
UI tweaks & minor cleanup
ui/components/*, ui/app/workspace/logs/views/logDetailsSheet.tsx, ui/components/ui/*, various small files
Visual class adjustments, dropdown in logs sheet, sheet header/close button tweaks, switch default size change.
Docs & migration guide
docs/plugins/{getting-started,writing-plugin,migration-guide}.mdx, docs/docs.json
Adds migration guide and v1.4.x+ doc tabs describing HTTPTransportMiddleware and execution order; docs updated for plugin migration.
Workflow & scripts
.github/workflows/*, .github/workflows/scripts/*
CI scripts updated for branch-aware pulls/pushes and release branches; release workflow branches updated.
Dependency updates
examples/plugins/hello-world/go.mod, plugins/governance/go.mod, transports/go.mod, core/go.mod
Adds/updates direct dependencies (fasthttp, sonic, pprof), and indirect bumps.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant HTTPHandler
    participant TracingMW as TracingMiddleware
    participant Tracer
    participant PluginPipeline
    participant PluginMW as HTTPTransportMiddleware
    participant Provider

    Client->>HTTPHandler: HTTP request
    HTTPHandler->>TracingMW: pass RequestCtx
    TracingMW->>Tracer: CreateTrace() / StartSpan
    TracingMW->>PluginPipeline: execute with traced ctx
    PluginPipeline->>PluginMW: middleware(next)
    PluginMW->>PluginMW: modify req/ctx, call next(ctx)
    PluginMW->>Provider: forward to provider
    Provider-->>PluginMW: response/chunk(s)
    PluginMW-->>PluginPipeline: response
    PluginPipeline-->>TracingMW: response
    TracingMW->>Tracer: EndSpan / EndTrace (finalize)
    TracingMW->>OtelPlugin: Inject(ctx, trace)  %% Observability plugins receive completed trace
    TracingMW-->>HTTPHandler: response sent (stream finalizer may run after flush)
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Suggested reviewers

  • danpiths

Poem

🐰
I hopped through middleware, light and spry,
Spans stitched paths where requests fly by.
First tokens timed, traces sent on cue,
Observability carrots — crunchy and new!
A tiny rabbit cheers: hop, trace, review!

Pre-merge checks and finishing touches

❌ Failed checks (1 inconclusive)
Check name Status Explanation Resolution
Title check ❓ Inconclusive The title 'plugins v2 architecture' is vague and does not clearly convey the main change: replacing TransportInterceptor with HTTPTransportMiddleware. Consider a more specific title like 'Replace TransportInterceptor with HTTPTransportMiddleware in plugin interface' to better describe the primary architectural change.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description covers all major required sections: Summary, Changes, Type of change, Affected areas, How to test, Breaking changes with migration instructions, and Security considerations. All essential information is present and well-organized.
Docstring Coverage ✅ Passed Docstring coverage is 93.39% which is sufficient. The required threshold is 80.00%.
✨ Finishing touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 11-19-plugins_v2_architecture

📜 Recent review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 828d3d7 and ee5875b.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (2)
  • core/go.mod
  • transports/go.mod
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/go.mod
  • transports/go.mod
🔇 Additional comments (3)
core/go.mod (2)

50-50: ✓ pprof dependency update aligns with PR objectives.

The version bump for github.com/google/pprof (to add dev profiling capabilities) is justified and recent.


5-69: Verify observability/tracing subsystem dependencies.

The AI summary mentions the PR adds a "tracing/observability subsystem" with Trace/Span models, Tracer, and TraceStore. However, I don't see corresponding observability dependencies (e.g., OpenTelemetry, Google Cloud Trace) in this core/go.mod. This could be intentional (stdlib-only implementation, or dependencies in other modules), but please confirm:

  1. Are observability/tracing dependencies provided by other modules in the stack (e.g., separate go.mod files for plugins or transports)?
  2. If tracing is stdlib-only in this module, are external observability integrations optional/deferred to plugins?

Per the coding guidelines, please review in light of the entire stack (#1170, #1169, #1121, #1120, #1119, #888) to ensure no required dependencies are missing.

transports/go.mod (1)

10-10: pprof is correctly added as a direct dependency.

The github.com/google/pprof dependency is directly imported in transports/bifrost-http/handlers/devpprof.go (line 13: "github.com/google/pprof/profile") and actively used for profiling functionality (e.g., runtime/pprof.WriteHeapProfile() at line 262). The dependency is appropriately gated behind dev mode checks and properly lifecycle-managed in the HTTP server. No changes are needed.

Likely an incorrect or invalid review comment.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions
Copy link
Contributor

🧪 Test Suite Available

This PR can be tested by a repository admin.

Run tests for PR #888

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch 2 times, most recently from ec9703b to de6897f Compare November 20, 2025 13:03
@akshaydeo akshaydeo marked this pull request as ready for review November 20, 2025 13:06
@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from de6897f to e32d6a2 Compare November 20, 2025 13:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
core/schemas/plugin.go (1)

48-48: Update comment reference to use HTTPTransportMiddleware.

Line 48 still references "TransportInterceptor" in the execution order documentation. This should be updated to "HTTPTransportMiddleware" for consistency.

Apply this diff:

-// 1. TransportInterceptor (HTTP transport only, modifies raw headers/body before entering Bifrost core)
+// 1. HTTPTransportMiddleware (HTTP transport only, modifies raw headers/body before entering Bifrost core)
transports/bifrost-http/handlers/middlewares.go (1)

48-81: Critical: Middleware execution instead of composition.

Line 78 immediately invokes the chained handler with (ctx) instead of returning it. This breaks the middleware pattern—the function should return a handler, not execute it.

Apply this diff:

-			lib.ChainMiddlewares(next, pluginsMiddlewareChain...)(ctx)			
+			lib.ChainMiddlewares(next, pluginsMiddlewareChain...)(ctx)
 		}
 	}
 }

Wait, let me reconsider. Looking at the structure:

func TransportInterceptorMiddleware(config *lib.Config) schemas.BifrostHTTPMiddleware {
	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
		return func(ctx *fasthttp.RequestCtx) {
			// ... logic ...
			lib.ChainMiddlewares(next, pluginsMiddlewareChain...)(ctx)
		}
	}
}

Actually, the outer function returns func(next) handler, and the inner function returns func(ctx). At line 78, we're inside the innermost function that takes ctx, so calling (ctx) makes sense here. But the issue is that lib.ChainMiddlewares returns a handler, and we need to call it with ctx.

Actually, on closer inspection, this looks correct. The pattern is:

  1. TransportInterceptorMiddleware returns a middleware (a function that takes a handler and returns a handler)
  2. That middleware's returned handler is a function that takes ctx
  3. Inside that function, we chain middlewares and invoke the result with ctx

So the code is correct. Let me reconsider...

Actually, I need to look more carefully. Line 78:

lib.ChainMiddlewares(next, pluginsMiddlewareChain...)(ctx)

This chains the middlewares starting with next as the base handler, wrapping it with the plugin middlewares, and then calls the resulting handler with ctx. This seems correct for the pattern.

But wait - there's a potential issue. If any of the earlier paths call next(ctx), and then we also call the chained middlewares, we'd be calling the handler twice. Let me check the early returns...

Lines 54-56: If no plugins, call next(ctx) and return ✓
Lines 66-68: If no governance, call next(ctx) and return ✓
Line 78: Otherwise, call chained middlewares

This looks correct actually. My initial concern was wrong.

However, I notice another issue: if a plugin returns nil for HTTPTransportMiddleware(), we skip it (lines 73-75), but what if ALL plugins return nil? Then pluginsMiddlewareChain would be empty, and we'd call ChainMiddlewares with just next and empty variadic args, which should work fine according to lines 12-15 of lib/middleware.go.

Actually, I think the code is correct. Let me reconsider if there are any other issues...

One thing I notice: the governance skip logic (lines 57-68) only executes the middleware chain if the governance plugin is loaded. This seems like a significant behavioral change - previously, TransportInterceptor would be called on all plugins, but now the HTTP middleware is only chained if governance is present. This might be intentional but should be verified.
[scratchpad_end] -->

♻️ Duplicate comments (2)
plugins/telemetry/main.go (1)

278-281: Same nil-middleware semantics concern as logging plugin

This HTTPTransportMiddleware stub mirrors logging’s pattern (returning nil to signal “no transport middleware”). That’s fine as long as the framework checks for nil before chaining plugin middlewares; otherwise a nil call will panic.

Please ensure the shared plugin wiring handles nil consistently; no change needed here beyond that.

plugins/mocker/main.go (1)

483-486: Mocker plugin’s HTTPTransportMiddleware stub follows the same pattern

Returning nil here is consistent with the other plugins and is a reasonable “no HTTP middleware” signal, provided the caller treats nil as “don’t apply this middleware” rather than invoking it.

Given the repeated pattern, it’s worth double‑checking the central wiring once to ensure nils are filtered out before chaining.

🧹 Nitpick comments (3)
plugins/governance/go.mod (1)

10-11: Verify version appropriateness and assess architectural necessity of direct fasthttp/sonic dependencies.

The governance plugin now has direct dependencies on bytedance/sonic and valyala/fasthttp, which aligns with the new HTTPTransportMiddleware pattern. However, clarify:

  1. Whether these versions are secure and stable (especially given the "chill" review context, but still important).
  2. Whether the governance plugin itself directly uses these libraries, or if they're being exposed as transitive dependencies from the core/framework modules.

If the governance plugin doesn't directly instantiate fasthttp handlers or use sonic for serialization, consider whether these dependencies should be imported only in the transport layer (core or framework) rather than propagating to plugins.

transports/bifrost-http/handlers/health.go (1)

9-32: HealthHandler middleware type updated; small comment mismatch

The change to ...schemas.BifrostHTTPMiddleware plus the new schemas import correctly aligns the health route with the shared middleware type and keeps /health behavior intact.

The doc comment for getHealth still says GET /api/health while the actual route is /health; consider updating the comment to avoid confusion.

plugins/logging/main.go (1)

186-189: Document and validate the semantics of a nil HTTPTransportMiddleware

HTTPTransportMiddleware returning nil is a reasonable “no-op” implementation, but only if the transport wiring checks for nil before composing middleware chains. Otherwise, a nil call could panic at runtime.

I’d recommend:

  • Treating nil explicitly as “no middleware” in the code that collects and applies plugin middlewares, and
  • Leaving this method as-is here, given the comment and to avoid extra fasthttp dependencies.
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d6626f2 and e32d6a2.

⛔ Files ignored due to path filters (1)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (32)
  • core/schemas/plugin.go (3 hunks)
  • examples/plugins/hello-world/go.mod (1 hunks)
  • examples/plugins/hello-world/main.go (2 hunks)
  • framework/plugins/dynamicplugin.go (3 hunks)
  • framework/plugins/dynamicplugin_test.go (2 hunks)
  • plugins/governance/go.mod (1 hunks)
  • plugins/governance/main.go (2 hunks)
  • plugins/jsonparser/main.go (1 hunks)
  • plugins/logging/main.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/mocker/main.go (1 hunks)
  • plugins/otel/main.go (1 hunks)
  • plugins/semanticcache/main.go (1 hunks)
  • plugins/telemetry/main.go (1 hunks)
  • transports/bifrost-http/handlers/cache.go (1 hunks)
  • transports/bifrost-http/handlers/config.go (2 hunks)
  • transports/bifrost-http/handlers/governance.go (2 hunks)
  • transports/bifrost-http/handlers/health.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/integrations.go (2 hunks)
  • transports/bifrost-http/handlers/logging.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/handlers/middlewares.go (5 hunks)
  • transports/bifrost-http/handlers/middlewares_test.go (8 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/handlers/providers.go (1 hunks)
  • transports/bifrost-http/handlers/session.go (2 hunks)
  • transports/bifrost-http/handlers/ui.go (2 hunks)
  • transports/bifrost-http/handlers/websocket.go (2 hunks)
  • transports/bifrost-http/integrations/router.go (2 hunks)
  • transports/bifrost-http/lib/middleware.go (1 hunks)
  • transports/bifrost-http/server/server.go (4 hunks)
🧰 Additional context used
🧬 Code graph analysis (29)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/integrations/router.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/logging/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (4)
transports/bifrost-http/lib/config.go (1)
  • Config (139-169)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
transports/bifrost-http/handlers/inference.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/governance/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/server/server.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-25)
🔇 Additional comments (38)
examples/plugins/hello-world/go.mod (2)

10-22: Indirect dependencies properly declared.

The transitive dependencies from fasthttp are correctly listed with // indirect markers. This formatting aligns with Go module best practices and makes the dependency graph transparent.


5-8: ---

Dependencies are appropriately versioned for the middleware architecture migration.

The addition of github.com/valyala/fasthttp v1.67.0 and github.com/maximhq/bifrost/core v1.2.27 correctly supports the HTTPTransportMiddleware pattern. Both versions are valid and stable, with fasthttp v1.67.0 including security fixes for URL validation and host parsing.

transports/bifrost-http/handlers/session.go (1)

11-39: Align SessionHandler middleware type with core schemas

RegisterRoutes now uses ...schemas.BifrostHTTPMiddleware and the new schemas import; this cleanly aligns session routes with the centralized middleware type while preserving the existing routing behavior via lib.ChainMiddlewares.

transports/bifrost-http/handlers/cache.go (1)

26-29: CacheHandler RegisterRoutes migrated to schemas middleware type

Using ...schemas.BifrostHTTPMiddleware for cache routes is consistent with the shared middleware abstraction and works seamlessly with lib.ChainMiddlewares.

transports/bifrost-http/handlers/logging.go (1)

41-47: LoggingHandler now uses schemas.BifrostHTTPMiddleware

Switching RegisterRoutes to ...schemas.BifrostHTTPMiddleware standardizes logging routes on the new middleware type without altering any logging behavior.

transports/bifrost-http/handlers/providers.go (1)

78-86: ProviderHandler RegisterRoutes aligned with schemas middleware

The provider management routes now take ...schemas.BifrostHTTPMiddleware, matching the shared middleware type across the transport layer while preserving all existing provider CRUD semantics.

transports/bifrost-http/handlers/mcp.go (1)

42-50: MCPHandler routes migrated to schemas.BifrostHTTPMiddleware

Using ...schemas.BifrostHTTPMiddleware here brings MCP endpoints in line with the new middleware contract while keeping all MCP behavior and routing identical.

transports/bifrost-http/handlers/integrations.go (1)

5-40: IntegrationHandler standardized on schemas.BifrostHTTPMiddleware

Adding the schemas import and updating RegisterRoutes to ...schemas.BifrostHTTPMiddleware cleanly propagates the new middleware type into all integration extensions via extension.RegisterRoutes(r, middlewares...) without changing routing semantics.

transports/bifrost-http/handlers/config.go (1)

14-55: ConfigHandler routes moved to schemas.BifrostHTTPMiddleware

The added schemas import and updated RegisterRoutes signature (...schemas.BifrostHTTPMiddleware) bring config and version endpoints in line with the unified middleware abstraction while leaving all config behavior as-is.

transports/bifrost-http/handlers/plugins.go (1)

53-59: Aligning PluginsHandler with shared middleware type looks correct

Switching RegisterRoutes to ...schemas.BifrostHTTPMiddleware keeps handler wiring consistent with the new central middleware contract while preserving existing route behavior via lib.ChainMiddlewares. No issues from this change alone.

transports/bifrost-http/handlers/middlewares_test.go (1)

6-6: Tests correctly updated to use the shared BifrostHTTPMiddleware type

Switching the middleware constructions to schemas.BifrostHTTPMiddleware (with the new import) keeps the test behavior identical while aligning with the central middleware type definition. The ordering, short‑circuit, and context‑mutation scenarios are all still clearly exercised, and the explicit conversions are type‑correct.

Also applies to: 295-301, 322-342, 368-373, 395-402, 405-418, 459-465, 467-475, 477-483

transports/bifrost-http/handlers/governance.go (1)

15-22: Governance routes now correctly use the shared middleware contract

Updating RegisterRoutes to accept ...schemas.BifrostHTTPMiddleware and leaving the lib.ChainMiddlewares calls intact cleanly aligns governance handlers with the new middleware model without changing route behavior.

Also applies to: 154-175

transports/bifrost-http/handlers/inference.go (1)

274-285: Inference handler middleware signature migration is consistent

CompletionHandler.RegisterRoutes now taking ...schemas.BifrostHTTPMiddleware matches the repository‑wide middleware type migration and should remain compatible with lib.ChainMiddlewares as updated in this PR.

examples/plugins/hello-world/main.go (1)

8-9: Example HTTPTransportMiddleware implementation looks correct

The hello‑world plugin’s HTTPTransportMiddleware has the expected func(next fasthttp.RequestHandler) fasthttp.RequestHandler signature, logs once per request, and correctly forwards to next(ctx), making it a clear, minimal example for plugin authors.

Also applies to: 20-25

transports/bifrost-http/integrations/router.go (2)

70-70: LGTM! Consistent middleware type migration.

The migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware is clean and consistent across both the interface definition and implementation. This centralizes the middleware type in the schemas package, improving code organization.

Also applies to: 232-232


11-11: LGTM! Clean middleware type migration.

The handler correctly adopts the new schemas.BifrostHTTPMiddleware type with the necessary import addition. Consistent with the broader architectural refactor.

Also applies to: 29-29

transports/bifrost-http/handlers/websocket.go (1)

14-14: LGTM! Consistent with the middleware migration.

The WebSocket handler correctly adopts the new middleware type from the schemas package.

Also applies to: 51-51

framework/plugins/dynamicplugin_test.go (1)

53-83: LGTM! Well-structured test for the new middleware API.

The test correctly verifies the middleware pattern:

  1. Creates a mock next handler
  2. Wraps it with the plugin's middleware
  3. Verifies the wrapped handler calls the next handler

This properly validates the middleware contract established by the new HTTPTransportMiddleware API.

plugins/jsonparser/main.go (1)

87-90: LGTM! Appropriate no-op middleware.

The plugin correctly implements the new HTTPTransportMiddleware method, returning nil to indicate no middleware is needed. This is the expected pattern for plugins that don't require HTTP transport interception.

core/schemas/plugin.go (1)

36-38: LGTM! Well-defined middleware type.

The BifrostHTTPMiddleware type follows the standard fasthttp middleware pattern: func(next fasthttp.RequestHandler) fasthttp.RequestHandler. This is a clean and idiomatic approach for HTTP middleware.

plugins/semanticcache/main.go (1)

338-341: LGTM! Consistent no-op middleware implementation.

The semantic cache plugin correctly implements the new middleware API, returning nil as it doesn't require HTTP transport interception.

plugins/otel/main.go (1)

146-149: LGTM! Consistent with the plugins v2 architecture.

The OTEL plugin correctly implements the new HTTPTransportMiddleware method, returning nil as it doesn't need HTTP-level middleware.

transports/bifrost-http/lib/middleware.go (1)

3-23: LGTM! Clean type migration.

The refactor correctly centralizes the middleware type definition in core/schemas while preserving the chaining logic.

transports/bifrost-http/handlers/middlewares.go (3)

20-45: LGTM! Type signature updated correctly.

The CORS middleware logic remains unchanged; only the return type was updated to use the centralized type definition.


99-200: LGTM! Type signature updated correctly.

The authentication middleware logic is preserved; only the return type signature was updated.


57-68: Governance gate is functionally correct but adds hidden behavioral coupling.

The governance gate (lines 57-68) is safe—only the governance plugin provides non-nil middleware, so gating execution when governance is absent doesn't skip any plugins. However, it introduces a brittle assumption: the code assumes governance is the exclusive middleware provider. If another plugin adds middleware in the future, it would silently bypass the gate and never execute.

Rather than gating on a specific plugin name, the code should filter collected middlewares and check if any are non-nil, making intent explicit and future-proof.

pluginsMiddlewareChain := []schemas.BifrostHTTPMiddleware{}
for _, plugin := range plugins {
	middleware := plugin.HTTPTransportMiddleware()
	if middleware == nil {
		continue
	}
	pluginsMiddlewareChain = append(pluginsMiddlewareChain, middleware)
}
if len(pluginsMiddlewareChain) > 0 {
	lib.ChainMiddlewares(next, pluginsMiddlewareChain...)(ctx)
} else {
	next(ctx)
}
plugins/maxim/main.go (1)

125-128: LGTM! Correct no-op middleware implementation.

The maxim plugin correctly returns nil for HTTP transport middleware since it operates at the PreHook/PostHook level rather than the HTTP transport layer.

transports/bifrost-http/server/server.go (5)

844-850: LGTM! Method signature updated correctly.

The route registration methods now properly accept the centralized middleware type.


853-930: LGTM! Consistent middleware type usage.

All handler registrations correctly use the updated middleware type throughout the method.


933-936: LGTM! UI route registration updated correctly.

The method signature properly reflects the middleware type change.


965-976: LGTM! Common middleware preparation updated correctly.

The method correctly builds and returns a slice of the centralized middleware type.


1077-1082: LGTM! Server middleware chain configured correctly.

The middleware composition properly chains CORS → TransportInterceptor → Router, ensuring the correct execution order.

plugins/governance/main.go (3)

158-174: LGTM! Middleware structure and early validation correct.

The middleware factory pattern is properly implemented with appropriate early returns for missing or invalid virtual keys.


195-209: LGTM! Load balancing integration correct.

The middleware properly modifies the request body with provider load balancing before forwarding to the next handler.


213-305: LGTM! Load balancing logic is sound.

The weighted random selection with budget/rate limit awareness and fallback handling is well-implemented with appropriate defensive checks.

framework/plugins/dynamicplugin.go (2)

26-40: LGTM! Dynamic plugin API updated correctly.

The field and method changes properly align with the new middleware-based plugin interface.


141-148: LGTM! Symbol lookup updated correctly.

The dynamic plugin loader properly looks up and casts the new HTTPTransportMiddleware symbol with clear error messages.

transports/bifrost-http/handlers/ui.go (1)

11-31: Middleware type migration to core/schemas looks consistent

Switching RegisterRoutes to use schemas.BifrostHTTPMiddleware and importing core/schemas aligns this handler with the shared middleware contract used elsewhere. The variadic signature and continued use of lib.ChainMiddlewares keep the API surface unchanged for callers while centralizing the type definition in core.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch 2 times, most recently from 9be42ea to c08c82f Compare November 24, 2025 07:22
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/schemas/plugin.go (1)

71-80: Update execution-order docs to reference HTTPTransportMiddleware instead of TransportInterceptor

The Plugin interface now exposes HTTPTransportMiddleware() but the execution-order comments above still refer to TransportInterceptor; updating those comments to describe HTTPTransportMiddleware and its place in the pipeline will avoid confusion for plugin authors.

♻️ Duplicate comments (1)
plugins/governance/main.go (1)

214-227: Fix misleading log message on JSON unmarshal error

The log message at the sonic.Unmarshal failure path still says “failed to marshal”, which is inaccurate and repeats a previously flagged issue.

Apply this diff to clarify the error:

-			err = sonic.Unmarshal(ctx.Request.Body(), &payload)
-			if err != nil {
-				p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+			err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+			if err != nil {
+				p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)
 				next(ctx)
 				return
 			}
🧹 Nitpick comments (3)
transports/bifrost-http/lib/middleware.go (1)

3-22: Middleware chaining now correctly standardizes on schemas.BifrostHTTPMiddleware

Switching ChainMiddlewares to use schemas.BifrostHTTPMiddleware keeps behavior identical while centralizing the type in schemas; just ensure all callers continue to filter out nil middlewares, or consider skipping nil inside the loop as a small safety net in the future.

plugins/governance/main.go (2)

13-20: HTTP middleware and virtual key parsing look sound; consider minor robustness tweaks

The new parseVirtualKey + HTTPTransportMiddleware flow is well-structured: it cleanly short-circuits when there’s no/invalid VK, validates via p.store.GetVirtualKey, enriches headers via addMCPIncludeTools, and rewrites the JSON body with loadBalanceProvider while always falling back to next(ctx) on errors. That gives safe behavior even with malformed or non-JSON bodies.

Two small optional improvements:

  1. Body presence check
    ctx.Request.Body() can be non‑nil but empty; in that case sonic.Unmarshal will just fail and you fall back, which is safe but noisy. You could avoid unnecessary log noise with a length check:
-			if ctx.Request.Body() == nil {
+			if len(ctx.Request.Body()) == 0 {
 				next(ctx)
 				return
 			}
  1. Unused error from addMCPIncludeTools
    addMCPIncludeTools never returns a non‑nil error, so the err handling here is effectively dead. Either drop the error from the helper’s signature (if you control all call sites) or add a brief comment noting that it currently can’t fail to avoid confusion for future readers.

Both are non‑blocking polish items; functionally this middleware looks correct.

Also applies to: 158-181, 183-235


70-79: Update Init doc comment to reference HTTPTransportMiddleware instead of TransportInterceptor

The Init comment still mentions TransportInterceptor for how inMemoryStore is used, but the implementation has moved that responsibility into HTTPTransportMiddleware. Updating the comment will prevent confusion for future maintainers.

For example:

-//   - `inMemoryStore` is used by TransportInterceptor to validate configured providers
+//   - `inMemoryStore` is used by HTTPTransportMiddleware to validate configured providers
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9be42ea and c08c82f.

⛔ Files ignored due to path filters (1)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (32)
  • core/schemas/plugin.go (3 hunks)
  • examples/plugins/hello-world/go.mod (1 hunks)
  • examples/plugins/hello-world/main.go (2 hunks)
  • framework/plugins/dynamicplugin.go (3 hunks)
  • framework/plugins/dynamicplugin_test.go (2 hunks)
  • plugins/governance/go.mod (1 hunks)
  • plugins/governance/main.go (3 hunks)
  • plugins/jsonparser/main.go (1 hunks)
  • plugins/logging/main.go (1 hunks)
  • plugins/maxim/main.go (1 hunks)
  • plugins/mocker/main.go (1 hunks)
  • plugins/otel/main.go (1 hunks)
  • plugins/semanticcache/main.go (1 hunks)
  • plugins/telemetry/main.go (1 hunks)
  • transports/bifrost-http/handlers/cache.go (1 hunks)
  • transports/bifrost-http/handlers/config.go (2 hunks)
  • transports/bifrost-http/handlers/governance.go (2 hunks)
  • transports/bifrost-http/handlers/health.go (2 hunks)
  • transports/bifrost-http/handlers/inference.go (1 hunks)
  • transports/bifrost-http/handlers/integrations.go (2 hunks)
  • transports/bifrost-http/handlers/logging.go (1 hunks)
  • transports/bifrost-http/handlers/mcp.go (1 hunks)
  • transports/bifrost-http/handlers/middlewares.go (3 hunks)
  • transports/bifrost-http/handlers/middlewares_test.go (8 hunks)
  • transports/bifrost-http/handlers/plugins.go (1 hunks)
  • transports/bifrost-http/handlers/providers.go (1 hunks)
  • transports/bifrost-http/handlers/session.go (2 hunks)
  • transports/bifrost-http/handlers/ui.go (2 hunks)
  • transports/bifrost-http/handlers/websocket.go (2 hunks)
  • transports/bifrost-http/integrations/router.go (2 hunks)
  • transports/bifrost-http/lib/middleware.go (1 hunks)
  • transports/bifrost-http/server/server.go (4 hunks)
🚧 Files skipped from review as they are similar to previous changes (13)
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/config.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/session.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/health.go
  • plugins/semanticcache/main.go
  • plugins/otel/main.go
  • plugins/governance/go.mod
🧰 Additional context used
🧬 Code graph analysis (18)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (51-53)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/logging/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (20-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
🔇 Additional comments (20)
examples/plugins/hello-world/go.mod (2)

7-7: fasthttp addition aligns with middleware pattern transition.

The addition of github.com/valyala/fasthttp v1.67.0 as a direct dependency is an expected change that aligns with the PR's transition to an HTTP middleware pattern. The transitive indirect dependencies (brotli, compress, bytebufferpool) are standard fasthttp dependencies and are appropriate.


6-6: Unable to access repository files for verification.

The review comment references examples/plugins/hello-world/go.mod, but I cannot access the codebase files in the current sandbox environment to independently verify the core dependency downgrade claim or check version consistency across other modules in the repository.

The original analysis flagged a downgrade of github.com/maximhq/bifrost/core from v1.2.29 to v1.2.27 as potentially problematic. Without access to the actual files, I cannot confirm:

  • Whether this downgrade is intentional
  • If version consistency exists across other modules
  • What functionality or security implications might be affected
transports/bifrost-http/handlers/providers.go (1)

78-78: LGTM! Middleware type migration is consistent.

The update from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware aligns with the architectural shift to centralize the middleware type definition in the core schemas package.

transports/bifrost-http/handlers/ui.go (1)

11-11: LGTM! Clean type migration with proper import.

The import addition and middleware type update are consistent with the broader migration to schemas.BifrostHTTPMiddleware.

Also applies to: 29-29

transports/bifrost-http/handlers/logging.go (1)

42-42: LGTM! Middleware type updated consistently.

The RegisterRoutes signature update to schemas.BifrostHTTPMiddleware is consistent with the architectural refactor across the codebase.

transports/bifrost-http/handlers/plugins.go (1)

53-53: LGTM! Type migration applied consistently.

The middleware type update maintains consistency with the broader architectural shift to centralize middleware types in the schemas package.

plugins/jsonparser/main.go (1)

87-90: LGTM! Plugin correctly implements new middleware interface.

The implementation of HTTPTransportMiddleware() returning nil is appropriate for this plugin, as indicated by the comment. This aligns with the migration from TransportInterceptor to the new middleware-based approach.

transports/bifrost-http/handlers/mcp.go (1)

42-42: LGTM! Middleware type migration is consistent.

The RegisterRoutes signature update follows the standard pattern for migrating to schemas.BifrostHTTPMiddleware.

transports/bifrost-http/handlers/middlewares_test.go (1)

6-6: LGTM! Tests updated consistently with type migration.

All test middleware references have been properly updated from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware. The test logic remains unchanged, ensuring the migration is purely structural.

Also applies to: 295-295, 322-322, 329-329, 336-336, 368-368, 395-395, 405-405, 413-413, 458-458, 467-467, 477-477

transports/bifrost-http/handlers/integrations.go (1)

8-8: LGTM! Integration handler properly migrated.

The import addition and RegisterRoutes signature update are consistent with the architectural refactor. The variadic middleware delegation to extension routers remains functionally identical.

Also applies to: 35-35

framework/plugins/dynamicplugin_test.go (1)

53-83: Dynamic plugin middleware test correctly validates chaining behavior

The new HTTPTransportMiddleware subtest exercises the middleware constructor, wrapped handler, and ensures next is invoked with a realistic fasthttp.RequestCtx setup; this is sufficient to validate the new dynamic-plugin hook wiring.

plugins/logging/main.go (1)

186-189: No-op HTTPTransportMiddleware is a valid opt-out for this plugin

Returning nil here is a clean way for the logging plugin to signal “no HTTP-level middleware”, and the transport layer already guards against nil middlewares before chaining.

examples/plugins/hello-world/main.go (1)

8-27: Example plugin’s HTTPTransportMiddleware correctly implements the new contract

The exported HTTPTransportMiddleware returns a schemas.BifrostHTTPMiddleware that logs then calls next, which is exactly what the dynamic plugin loader expects for the new HTTP transport hook.

transports/bifrost-http/handlers/websocket.go (1)

14-53: WebSocket route registration now aligned with shared middleware type

Using ...schemas.BifrostHTTPMiddleware in RegisterRoutes and feeding them through lib.ChainMiddlewares aligns WebSocket routes with the unified middleware abstraction without changing behavior.

core/schemas/plugin.go (1)

4-39: BifrostHTTPMiddleware type cleanly defines the shared HTTP middleware contract

Defining BifrostHTTPMiddleware in schemas gives a single, clear function type for all HTTP middleware, and using fasthttp.RequestHandler directly matches the transport layer’s expectations.

plugins/mocker/main.go (1)

479-486: Mocker plugin cleanly opts out of HTTP transport middleware

Returning nil from MockerPlugin.HTTPTransportMiddleware is consistent with the interface and works with the interceptor middleware’s nil filtering to indicate this plugin has no HTTP-layer behavior.

transports/bifrost-http/handlers/middlewares.go (2)

18-44: CORS middleware correctly updated to use schemas.BifrostHTTPMiddleware

The CORS logic is unchanged and wrapping it in schemas.BifrostHTTPMiddleware matches the new shared middleware abstraction with no behavioral impact.


86-187: Auth middleware’s type change cleanly adopts the shared middleware abstraction

Changing AuthMiddleware to return schemas.BifrostHTTPMiddleware while keeping the behavior (including the identity middleware when auth is disabled or store is nil) preserves existing auth semantics and fits naturally into the new middleware chain.

plugins/maxim/main.go (1)

125-128: Nil HTTPTransportMiddleware is an acceptable no-op implementation

Returning nil here is a clear way to indicate this plugin does not participate in HTTP middleware and cleanly satisfies the updated Plugin interface. As long as the transport layer skips nil middlewares (as it does for other plugins in this PR), this is fine.

plugins/governance/main.go (1)

370-377: Updated PreHook error message better matches behavior

Changing the PreHook error message to “virtual key is missing or invalid” aligns with the actual semantics (empty/missing or rejected VK) and improves clarity for clients without altering logic.

@akshaydeo akshaydeo changed the base branch from main to graphite-base/888 December 22, 2025 09:12
@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from c08c82f to 13f8738 Compare December 22, 2025 09:12
@akshaydeo akshaydeo changed the base branch from graphite-base/888 to v1.4.0 December 22, 2025 09:12
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/schemas/plugin.go (1)

46-46: Update documentation to reflect new method name.

Line 46 still refers to "TransportInterceptor" in the execution order documentation, but the method has been renamed to "HTTPTransportMiddleware". Update the comment for consistency.

🔎 Proposed fix
-// 1. TransportInterceptor (HTTP transport only, modifies raw headers/body before entering Bifrost core)
+// 1. HTTPTransportMiddleware (HTTP transport only, modifies raw headers/body before entering Bifrost core)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c08c82f and 13f8738.

📒 Files selected for processing (1)
  • core/schemas/plugin.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • core/schemas/plugin.go
🧠 Learnings (1)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/plugin.go
🧬 Code graph analysis (1)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
🔇 Additional comments (3)
core/schemas/plugin.go (3)

4-6: LGTM! Import correctly added for middleware pattern.

The fasthttp import is necessary for the BifrostHTTPMiddleware type definition and follows the standard middleware pattern.


34-36: LGTM! Clean middleware type definition.

The BifrostHTTPMiddleware type correctly follows the standard middleware constructor pattern and is well-documented.


73-77: LGTM! Clean migration to middleware pattern.

The HTTPTransportMiddleware() method properly replaces the old TransportInterceptor approach with a standard middleware constructor pattern. The documentation clearly explains the new behavior.

Note: The hello-world example issue (missing method receiver) has already been flagged in previous review comments.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 13f8738 to fa4b8d4 Compare December 22, 2025 09:33
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
core/schemas/plugin.go (1)

46-46: Update comment to reference HTTPTransportMiddleware.

Line 46 still references the old method name "TransportInterceptor" in the execution order documentation.

🔎 Proposed fix
-// 1. TransportInterceptor (HTTP transport only, modifies raw headers/body before entering Bifrost core)
+// 1. HTTPTransportMiddleware (HTTP transport only, modifies raw headers/body before entering Bifrost core)
 // 2. PreHook (executed in registration order)
 // 3. Provider call
 // 4. PostHook (executed in reverse order of PreHooks)
♻️ Duplicate comments (3)
examples/plugins/hello-world/main.go (1)

19-26: Fix the hello-world example: HTTPTransportMiddleware must be a method with a receiver.

The HTTPTransportMiddleware function is defined as a free function without a receiver, which does not properly implement the Plugin interface. To correctly implement the interface, it should be a method on a plugin struct type (e.g., func (p *HelloWorldPlugin) HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware).

🔎 Proposed fix

First, define a plugin struct type near the top of the file:

+type HelloWorldPlugin struct{}
+
 func Init(config any) error {
 	fmt.Println("Init called")
-	return nil
+	return nil
 }

Then update the function to be a method on that struct:

-func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware {
+func (p *HelloWorldPlugin) HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
 			fmt.Println("HTTPTransportMiddleware called")
 			next(ctx)
 		}
 	}
 }

Similarly, update all other exported functions to be methods on the plugin struct (PreHook, PostHook, GetName, Cleanup).

transports/bifrost-http/handlers/middlewares.go (1)

47-68: Update comments to reflect the new middleware terminology.

The function and inline comments still reference "interceptors" (lines 47, 60) despite the code now using HTTPTransportMiddleware(). Update the comments to reference "HTTP transport middleware" for consistency with the new API.

plugins/governance/main.go (1)

212-217: Fix error message accuracy.

Line 214: The error message says "failed to marshal" but the operation is sonic.Unmarshal. This was flagged in a previous review.

🔎 Proposed fix
-			p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+			p.logger.Error("failed to unmarshal request body: %v", err)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 13f8738 and fa4b8d4.

⛔ Files ignored due to path filters (1)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (36)
  • core/mcp.go
  • core/schemas/plugin.go
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/main.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
💤 Files with no reviewable changes (1)
  • framework/configstore/tables/mcp.go
🚧 Files skipped from review as they are similar to previous changes (12)
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/session.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/lib/middleware.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/ui.go
  • plugins/semanticcache/main.go
  • plugins/governance/go.mod
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/inference.go
  • core/mcp.go
  • transports/bifrost-http/handlers/websocket.go
  • plugins/otel/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • core/schemas/plugin.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/health.go
  • plugins/logging/main.go
  • transports/go.mod
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/server/server.go
  • plugins/maxim/main.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/jsonparser/main.go
  • plugins/governance/main.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/inference.go
  • core/mcp.go
  • transports/bifrost-http/handlers/websocket.go
  • plugins/otel/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/health.go
  • plugins/logging/main.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/server/server.go
  • plugins/maxim/main.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/jsonparser/main.go
  • plugins/governance/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/mcp.go
🧬 Code graph analysis (20)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/inference.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/otel/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/middlewares.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/logging/main.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/inference.go (1)
  • TranscriptionRequest (285-289)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/server/server.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/middlewares.go (1)
  • TransportInterceptorMiddleware (48-69)
plugins/maxim/main.go (1)
core/schemas/plugin.go (2)
  • Plugin (69-95)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
🔇 Additional comments (29)
core/mcp.go (1)

1135-1150: In-process client now uses strongly typed InProcessServer

Passing config.InProcessServer directly into client.NewInProcessClient is fine and removes redundant runtime type assertions, assuming schemas.MCPClientConfig.InProcessServer is already declared as *server.MCPServer across the codebase.

Please confirm that InProcessServer is declared as *server.MCPServer (or a compatible concrete type) in schemas.MCPClientConfig so that this remains type-safe at compile time.

transports/bifrost-http/handlers/providers.go (1)

79-89: Provider routes now consume schemas.BifrostHTTPMiddleware

Updating RegisterRoutes to take ...schemas.BifrostHTTPMiddleware while still chaining via lib.ChainMiddlewares matches the new global middleware type and keeps the handler behavior unchanged.

transports/bifrost-http/handlers/config.go (1)

15-62: Config routes aligned to schemas.BifrostHTTPMiddleware

Importing core/schemas and updating RegisterRoutes to accept ...schemas.BifrostHTTPMiddleware (still passed through lib.ChainMiddlewares) keeps the runtime behavior but unifies the middleware type with the rest of the HTTP stack.

transports/bifrost-http/handlers/plugins.go (1)

52-59: Plugins handler now uses schema-level middleware type

Switching PluginsHandler.RegisterRoutes to ...schemas.BifrostHTTPMiddleware brings this handler in line with the new plugin middleware contract and with other HTTP handlers using the shared type.

transports/bifrost-http/handlers/inference.go (1)

364-390: Inference routes switched to schemas.BifrostHTTPMiddleware

Updating CompletionHandler.RegisterRoutes to take ...schemas.BifrostHTTPMiddleware while still delegating to lib.ChainMiddlewares cleanly adopts the new shared middleware type for all inference endpoints without changing routing behavior.

transports/bifrost-http/handlers/mcpServer.go (1)

71-76: MCP server routes migrated to schemas.BifrostHTTPMiddleware

MCPServerHandler.RegisterRoutes now accepts ...schemas.BifrostHTTPMiddleware, which is consistent with the rest of the HTTP stack and the centralized Bifrost middleware type; the POST/GET /mcp behavior remains the same.

examples/plugins/hello-world/go.mod (1)

6-23: go.mod: fasthttp dependency aligns with existing modules

Adding github.com/valyala/fasthttp v1.67.0 to the hello-world example is correct and already consistent across all modules in the codebase (core, framework, and other plugins). No version misalignment issues.

transports/go.mod (1)

5-25: mcp-go v0.41.1 dependency is consistent across all modules

The github.com/mark3labs/mcp-go v0.41.1 dependency in transports/go.mod matches all other modules in the repository. Verified that core, framework, all plugins, and test modules consistently use the same version.

transports/bifrost-http/handlers/websocket.go (1)

14-14: LGTM! Consistent middleware type migration.

The import addition and signature update align with the repository-wide migration to schemas.BifrostHTTPMiddleware. No functional changes to routing logic.

Also applies to: 51-51

transports/bifrost-http/handlers/health.go (1)

9-9: LGTM! Middleware type migration is consistent.

The import and signature changes align with the new schemas.BifrostHTTPMiddleware type used across the codebase.

Also applies to: 27-27

transports/bifrost-http/handlers/mcp.go (1)

43-51: LGTM! MCP handler aligned with new middleware pattern.

The signature update and route registrations properly use the new schemas.BifrostHTTPMiddleware type, maintaining existing MCP functionality.

transports/bifrost-http/handlers/integrations.go (1)

8-8: LGTM! Integration handler properly delegates new middleware type.

The import and signature changes align with the migration, and the method correctly passes the new middleware type to extension routers.

Also applies to: 37-41

plugins/logging/main.go (1)

196-199: LGTM! Logging plugin correctly implements new middleware interface.

The method properly replaces TransportInterceptor with HTTPTransportMiddleware using the correct receiver signature and returns nil (appropriate for plugins that don't need transport-layer interception).

core/schemas/plugin.go (1)

34-36: LGTM! Core middleware type and interface properly defined.

The BifrostHTTPMiddleware type follows standard middleware patterns, and the Plugin.HTTPTransportMiddleware() method signature is correctly defined.

Also applies to: 73-77

plugins/otel/main.go (1)

167-170: LGTM! OTEL plugin correctly migrated to new middleware interface.

The method signature with receiver properly implements the Plugin interface, and returning nil is appropriate for plugins that don't require transport-layer middleware.

plugins/mocker/main.go (1)

481-484: LGTM!

The HTTPTransportMiddleware() correctly returns nil since this plugin uses PreHook for its mocking logic rather than HTTP transport-level interception. This aligns with the new middleware pattern introduced in this PR.

transports/bifrost-http/handlers/middlewares.go (2)

19-19: LGTM!

Return type correctly updated to schemas.BifrostHTTPMiddleware, aligning with the new centralized middleware type definition.


87-87: LGTM!

Return type correctly updated to schemas.BifrostHTTPMiddleware.

plugins/maxim/main.go (1)

124-127: LGTM!

The HTTPTransportMiddleware() correctly returns nil since this plugin uses PreHook/PostHook for Maxim tracing integration rather than HTTP transport-level interception.

plugins/jsonparser/main.go (1)

86-89: LGTM!

The HTTPTransportMiddleware() correctly returns nil since this plugin uses PostHook for streaming JSON parsing rather than HTTP transport-level interception.

plugins/governance/main.go (3)

13-13: LGTM!

Using sonic for JSON marshaling in the hot path is a good choice for performance.


158-181: LGTM!

The parseVirtualKey helper correctly extracts virtual keys from multiple header sources (x-bf-vk, Authorization: Bearer, x-api-key) with proper prefix validation and priority ordering.


183-234: HTTPTransportMiddleware implementation follows the new middleware pattern correctly.

The middleware properly:

  • Extracts and validates virtual keys
  • Adds MCP include tools headers
  • Performs provider load balancing
  • Gracefully continues on errors (avoiding request failures for governance issues)

The implementation aligns with the PR's architecture shift from TransportInterceptor to HTTPTransportMiddleware.

framework/plugins/dynamicplugin.go (1)

24-39: Struct and method look good, pending the loader fix.

The field and accessor method are correctly structured. Once the symbol lookup is fixed to call the factory function, this will work as intended.

transports/bifrost-http/server/server.go (5)

958-965: LGTM!

Signature correctly updated to use the new schemas.BifrostHTTPMiddleware type. The route registration logic is unchanged.


968-968: LGTM!

Consistent type migration for the API routes registration.


1054-1057: LGTM!

Type migration applied consistently.


1086-1097: LGTM!

Return type and slice initialization correctly updated to use the new middleware type.


1230-1231: LGTM!

The TransportInterceptorMiddleware is correctly prepended to the inference middleware chain, ensuring plugin middlewares wrap the entire request lifecycle.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from fa4b8d4 to ddfbaf4 Compare December 22, 2025 09:43
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (3)
transports/bifrost-http/handlers/middlewares.go (1)

47-68: Comments still reference old "interceptor" terminology.

As noted in a previous review, the function name and inline comments still use "TransportInterceptor" terminology even though the code now uses plugin.HTTPTransportMiddleware(). Update the comment on line 47 to reference "HTTP transport middleware" and line 59's comment to say "Collect plugin HTTP transport middleware" instead of "Call TransportInterceptor on all plugins".

🔎 Suggested comment updates
-// TransportInterceptorMiddleware collects all plugin interceptors and calls them one by one
+// TransportInterceptorMiddleware collects all plugin HTTP transport middleware and chains them
 func TransportInterceptorMiddleware(config *lib.Config) schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
 			plugins := config.GetLoadedPlugins()
 			if len(plugins) == 0 {
 				next(ctx)
 				return
 			}
 			pluginsMiddlewareChain := []schemas.BifrostHTTPMiddleware{}
 			for _, plugin := range plugins {
 				middleware := plugin.HTTPTransportMiddleware()
-				// Call TransportInterceptor on all plugins
+				// Collect plugin HTTP transport middleware
 				if middleware == nil {
 					continue
 				}
plugins/governance/main.go (1)

212-217: Error message still incorrect after previous fix.

Line 214: The error message says "failed to marshal" but the operation on line 212 is sonic.Unmarshal. This was flagged in a previous review and marked as addressed in commit fa4b8d4, but the incorrect message remains in the current code.

🔎 Proposed fix
-				p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+				p.logger.Error("failed to unmarshal request body: %v", err)
framework/plugins/dynamicplugin.go (1)

140-147: Critical: Type mismatch will cause dynamic plugin loading to fail.

The example plugin at examples/plugins/hello-world/main.go:18-25 exports HTTPTransportMiddleware as a factory function:

func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware { ... }

However, line 145 attempts to cast the symbol directly to schemas.BifrostHTTPMiddleware:

if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {

This type mismatch will cause the assertion to always fail, preventing any dynamic plugins from loading successfully. The symbol is a function that returns the middleware, not the middleware itself.

Compare with the GetName pattern on line 137, which correctly handles factory functions.

🔎 Proposed fix
 	// Looking up for HTTPTransportMiddleware method
 	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
 	if err != nil {
 		return nil, err
 	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
 	}
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()
🧹 Nitpick comments (1)
core/schemas/plugin.go (1)

45-46: Update outdated comment terminology.

Line 46 still references "TransportInterceptor" in the execution order documentation, but the interface now uses HTTPTransportMiddleware. This should be updated for consistency.

🔎 Proposed fix
 // Execution order:
-// 1. TransportInterceptor (HTTP transport only, modifies raw headers/body before entering Bifrost core)
+// 1. HTTPTransportMiddleware (HTTP transport only, wraps request handling before entering Bifrost core)
 // 2. PreHook (executed in registration order)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fa4b8d4 and ddfbaf4.

⛔ Files ignored due to path filters (1)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (37)
  • core/mcp.go
  • core/schemas/plugin.go
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/main.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/components/sidebar.tsx
💤 Files with no reviewable changes (1)
  • framework/configstore/tables/mcp.go
✅ Files skipped from review due to trivial changes (1)
  • ui/components/sidebar.tsx
🚧 Files skipped from review as they are similar to previous changes (17)
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/cache.go
  • transports/go.mod
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/providers.go
  • plugins/governance/go.mod
  • core/mcp.go
  • plugins/maxim/main.go
  • examples/plugins/hello-world/go.mod
  • plugins/otel/main.go
  • plugins/semanticcache/main.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/config.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcpServer.go
  • core/schemas/plugin.go
  • plugins/mocker/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/server/server.go
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/health.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • plugins/jsonparser/main.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcpServer.go
  • core/schemas/plugin.go
  • plugins/mocker/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/server/server.go
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/health.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • plugins/jsonparser/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/middlewares.go
🧬 Code graph analysis (18)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/server/server.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/middlewares.go (1)
  • TransportInterceptorMiddleware (48-68)
transports/bifrost-http/lib/config.go (1)
  • Config (184-215)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/logging/main.go (4)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
ui/lib/types/logs.ts (1)
  • TranscriptionInput (15-17)
transports/bifrost-http/handlers/inference.go (1)
  • TranscriptionRequest (285-289)
transports/bifrost-http/handlers/middlewares.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
🔇 Additional comments (19)
transports/bifrost-http/handlers/middlewares_test.go (1)

308-308: LGTM! Consistent middleware type migration.

All test middleware instantiations have been correctly updated from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware. The test logic remains unchanged and continues to validate middleware chaining, execution order, context modification, and short-circuiting behavior.

Also applies to: 335-335, 342-342, 349-349, 381-381, 408-408, 418-418, 426-426, 471-471, 480-480, 490-490

transports/bifrost-http/handlers/logging.go (1)

42-42: LGTM! RegisterRoutes signature updated consistently.

The method signature has been updated to accept schemas.BifrostHTTPMiddleware instead of lib.BifrostHTTPMiddleware, aligning with the broader middleware type migration. The route registration logic remains unchanged.

transports/bifrost-http/handlers/ui.go (1)

11-11: LGTM! Middleware type migration completed.

The import and RegisterRoutes signature have been updated to use schemas.BifrostHTTPMiddleware, consistent with the codebase-wide migration. The UI serving logic remains unchanged.

Also applies to: 29-29

transports/bifrost-http/handlers/mcp.go (1)

43-43: LGTM! Signature updated for consistency.

The RegisterRoutes method now accepts schemas.BifrostHTTPMiddleware, aligning with the middleware type migration. All MCP route registrations continue to apply middlewares via ChainMiddlewares.

transports/bifrost-http/handlers/mcpServer.go (1)

72-72: LGTM! MCP server routes updated consistently.

The RegisterRoutes signature has been updated to accept schemas.BifrostHTTPMiddleware, maintaining consistency with the broader middleware type migration. The MCP server route handling remains unchanged.

transports/bifrost-http/handlers/integrations.go (1)

8-8: LGTM! Integration handler updated consistently.

The import and RegisterRoutes signature have been updated to use schemas.BifrostHTTPMiddleware. The method continues to delegate route registration to each integration extension with the provided middlewares.

Also applies to: 37-37

plugins/telemetry/main.go (1)

279-282: LGTM! Correct no-op middleware implementation.

The HTTPTransportMiddleware method correctly returns nil since this plugin doesn't require HTTP transport middleware. The telemetry plugin collects metrics through PreHook and PostHook methods instead, which is the appropriate pattern for request/response lifecycle tracking.

framework/plugins/dynamicplugin_test.go (1)

16-16: LGTM! Test properly validates new middleware API.

The test has been correctly updated to validate HTTPTransportMiddleware instead of the deprecated TransportInterceptor. The test:

  • Creates a mock next handler to track invocation
  • Obtains the middleware function from the plugin
  • Wraps the next handler with the middleware
  • Sets up a realistic request context with URI, method, and headers
  • Verifies the next handler is called through the middleware chain

This properly tests the new middleware-based invocation pattern.

Also applies to: 54-84

transports/bifrost-http/lib/middleware.go (1)

3-22: LGTM! Clean migration to schemas-based middleware type.

The transition from a local BifrostHTTPMiddleware type to schemas.BifrostHTTPMiddleware is straightforward and maintains the existing chaining logic correctly.

plugins/logging/main.go (1)

196-199: LGTM! Correct no-op middleware implementation.

The logging plugin appropriately returns nil for HTTP transport middleware since it operates via PreHook/PostHook rather than at the transport layer.

core/schemas/plugin.go (1)

73-77: LGTM! Clean middleware-based API design.

The new HTTPTransportMiddleware() method returning a BifrostHTTPMiddleware constructor follows standard middleware patterns and provides better composability than the previous interception approach.

plugins/jsonparser/main.go (1)

86-89: LGTM! Appropriate no-op middleware for this plugin.

The JSON parser plugin correctly returns nil since it operates on responses in PostHook rather than at the HTTP transport layer.

plugins/mocker/main.go (1)

481-484: LGTM! Correct no-op implementation.

The mocker plugin appropriately returns nil since it operates via PreHook to short-circuit requests, not at the HTTP transport layer.

transports/bifrost-http/handlers/health.go (1)

27-29: LGTM! Clean signature migration.

The health handler correctly adopts the new schemas.BifrostHTTPMiddleware type without any functional changes.

transports/bifrost-http/handlers/middlewares.go (1)

56-65: LGTM! Correct middleware collection and chaining.

The logic properly collects non-nil middleware from each plugin and chains them using lib.ChainMiddlewares, maintaining plugin ordering and allowing short-circuiting.

transports/bifrost-http/server/server.go (2)

958-958: LGTM! Consistent signature updates across server API.

All route registration and middleware preparation methods have been correctly updated to use schemas.BifrostHTTPMiddleware, maintaining consistency throughout the server initialization flow.

Also applies to: 968-968, 1054-1054, 1086-1087


1230-1230: LGTM! Correct middleware prepending.

The inference middleware chain correctly prepends TransportInterceptorMiddleware using the new schemas.BifrostHTTPMiddleware type.

plugins/governance/main.go (2)

409-409: LGTM!

The error message accurately reflects the validation failure condition.


183-234: HTTPTransportMiddleware operates at HTTP transport layer for request transformation, not governance enforcement.

The middleware correctly fails open when virtual keys are missing or errors occur in transformations (MCP tools, JSON parsing, load balancing). Governance enforcement is handled at a separate layer via PreHook, which respects the IsVkMandatory configuration setting:

  • When IsVkMandatory=false (default): missing virtual keys bypass HTTPTransportMiddleware transformations intentionally
  • When IsVkMandatory=true: PreHook rejects missing/invalid virtual keys with a 400 error before request processing

Errors in HTTPTransportMiddleware operations (JSON unmarshaling, load balancing) should not block requests since this layer focuses on request transformation for availability. However, to improve observability, add metrics/alerts for these error paths to ensure visibility when governance transformations fail silently.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from ddfbaf4 to d632297 Compare December 22, 2025 10:26
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (2)
plugins/governance/main.go (1)

158-181: Missing prefix validation for x-bf-vk header (previously flagged).

The x-bf-vk header on line 162 is returned without validating that it starts with VirtualKeyPrefix (sk-bf-), while the Authorization Bearer (line 168) and x-api-key (line 178) headers correctly validate the prefix. This inconsistency could allow invalid virtual keys to pass when provided via the x-bf-vk header.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
+			return bifrost.Ptr(vkValue)
+		}
 	}

Based on past review comments from commit history; this issue remains unaddressed.

framework/plugins/dynamicplugin.go (1)

140-147: Critical: Type mismatch in symbol lookup - this issue appears unresolved.

This is the same critical issue flagged in the previous review comment (marked as "✅ Addressed in commit ddfbaf4"), but the code still shows the incorrect pattern:

Line 145 attempts to cast httpTransportMiddlewareSym directly to schemas.BifrostHTTPMiddleware, but the example plugin at examples/plugins/hello-world/main.go exports HTTPTransportMiddleware as a factory function with signature func() schemas.BifrostHTTPMiddleware.

This cast will always fail because the symbol is a function that returns the middleware, not the middleware itself. Compare with the correct pattern used for GetName on line 137.

🔎 Required fix (same as previous review)
 	// Looking up for HTTPTransportMiddleware method
 	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
 	if err != nil {
 		return nil, err
 	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
 	}
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()

Note: The previous review indicated this was addressed in commit ddfbaf4, but the issue persists in the current code. Please verify whether the fix was reverted or if this PR is based on an older commit.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ddfbaf4 and d632297.

⛔ Files ignored due to path filters (1)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (41)
  • core/mcp.go
  • core/schemas/plugin.go
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/main.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/components/sidebar.tsx
💤 Files with no reviewable changes (1)
  • framework/configstore/tables/mcp.go
✅ Files skipped from review due to trivial changes (1)
  • docs/plugins/getting-started.mdx
🚧 Files skipped from review as they are similar to previous changes (14)
  • transports/bifrost-http/handlers/providers.go
  • core/mcp.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/handlers/session.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/schemas/plugin.go
  • transports/go.mod
  • transports/bifrost-http/handlers/cache.go
  • plugins/semanticcache/main.go
  • plugins/maxim/main.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • docs/docs.json
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/inference.go
  • docs/plugins/writing-plugin.mdx
  • plugins/logging/main.go
  • ui/components/sidebar.tsx
  • plugins/telemetry/main.go
  • plugins/governance/go.mod
  • docs/plugins/migration-guide.mdx
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/server/server.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/config.go
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/health.go
  • examples/plugins/hello-world/main.go
🧠 Learnings (2)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/logging/main.go
  • plugins/telemetry/main.go
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/server/server.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/config.go
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/health.go
  • examples/plugins/hello-world/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/health.go
🧬 Code graph analysis (19)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/inference.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/integrations/router.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/middlewares.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/server/server.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/middlewares.go (1)
  • TransportInterceptorMiddleware (48-68)
transports/bifrost-http/lib/config.go (1)
  • Config (184-215)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (36-36)
🔇 Additional comments (25)
ui/components/sidebar.tsx (2)

208-208: Verify whether this UI change belongs in this PR.

This styling change (reducing border radius from rounded-md to rounded-sm) appears unrelated to the PR's stated objectives, which focus on plugins v2 architecture and replacing TransportInterceptor with HTTPTransportMiddleware in the Go backend.

Consider whether this visual adjustment should be:

  • Moved to a separate UI-focused PR for clearer change tracking
  • Documented in the PR description if intentionally bundled

The change itself is technically correct.

Based on coding guidelines, which emphasize reviewing changes in light of the whole PR stack context.


243-243: Same styling adjustment as line 208.

This change maintains visual consistency with the main menu button adjustment, but the same concern about PR scope applies.

plugins/governance/go.mod (1)

8-11: Pinned dependency versions are current and secure.

The promotion of bytedance/sonic v1.14.1 and valyala/fasthttp v1.67.0 from indirect to direct dependencies aligns with the middleware refactor. v1.67.0 includes security fixes from multiple security researchers, with improvements to URL validation, IPv6 validation, scheme validation, and host validation. bytedance/sonic shows 0 unresolved vulnerabilities. These versions are consistent across all plugins in the PR stack.

docs/plugins/migration-guide.mdx (2)

1-307: Comprehensive and well-structured migration guide.

The documentation thoroughly covers the migration from TransportInterceptor to HTTPTransportMiddleware, including:

  • Clear comparison table of differences
  • Step-by-step migration instructions
  • Common migration patterns with before/after examples
  • Troubleshooting section for common issues

The code examples are consistent with the actual implementation in the codebase (e.g., examples/plugins/hello-world/main.go shows the same middleware pattern).


259-264: The documented execution order in the migration guide is accurate. No changes needed.

Likely an incorrect or invalid review comment.

docs/docs.json (1)

135-136: LGTM!

The migration guide is appropriately placed after the "writing-plugin" documentation in the navigation structure, providing a logical flow for plugin developers.

transports/bifrost-http/handlers/inference.go (1)

365-390: LGTM!

The RegisterRoutes signature update to accept schemas.BifrostHTTPMiddleware aligns with the project-wide migration to the new middleware pattern. The change is consistent with the type definition in core/schemas/plugin.go and other handler files in this PR.

transports/bifrost-http/handlers/plugins.go (1)

53-59: LGTM!

The RegisterRoutes signature update is consistent with the middleware type migration across all handler files in this PR.

plugins/telemetry/main.go (1)

279-282: LGTM - No-op implementation is appropriate.

The telemetry plugin correctly implements HTTPTransportMiddleware returning nil since it doesn't need to intercept HTTP transport requests. The framework safely filters out nil middlewares before chaining them. The plugin's metrics collection is handled via PreHook/PostHook for Bifrost-level metrics and the separate HTTPMiddleware method for HTTP request metrics.

transports/bifrost-http/handlers/config.go (1)

15-15: LGTM: Consistent type migration to schemas.BifrostHTTPMiddleware.

The import addition and signature change align with the broader architectural migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware. The routing logic and middleware chaining remain unchanged.

Also applies to: 55-62

transports/bifrost-http/handlers/websocket.go (1)

14-14: LGTM: Consistent type migration.

The import and signature change align with the project-wide migration to schemas.BifrostHTTPMiddleware. WebSocket route registration logic remains unchanged.

Also applies to: 51-53

transports/bifrost-http/handlers/ui.go (1)

11-11: LGTM: Consistent type migration.

The import and signature change align with the architectural migration to schemas.BifrostHTTPMiddleware. UI route registration logic remains unchanged.

Also applies to: 29-32

transports/bifrost-http/handlers/mcpServer.go (1)

72-76: LGTM: Consistent type migration.

The signature change aligns with the project-wide migration to schemas.BifrostHTTPMiddleware. MCP server route registration logic remains unchanged.

transports/bifrost-http/handlers/mcp.go (1)

43-51: LGTM: Consistent type migration.

The signature change aligns with the project-wide migration to schemas.BifrostHTTPMiddleware. MCP tool route registration logic remains unchanged.

plugins/logging/main.go (1)

196-199: LGTM: Appropriate no-op middleware implementation.

The logging plugin correctly returns nil from HTTPTransportMiddleware() since it performs its functionality through PreHook/PostHook rather than HTTP transport interception. This aligns with the new plugin architecture where middleware is optional.

framework/plugins/dynamicplugin_test.go (1)

54-84: LGTM: Comprehensive test coverage for new middleware API.

The test properly exercises the new HTTPTransportMiddleware() API by:

  1. Obtaining the middleware factory from the plugin
  2. Wrapping a mock next handler
  3. Invoking the wrapped handler
  4. Verifying the next handler is called

This validates the middleware chain pattern introduced by the plugins v2 architecture.

transports/bifrost-http/integrations/router.go (1)

72-73: LGTM: Consistent interface and implementation updates.

The ExtensionRouter interface and GenericRouter implementation both correctly update their RegisterRoutes signatures to accept schemas.BifrostHTTPMiddleware. The route registration logic and middleware chaining remain unchanged.

Also applies to: 320-368

transports/bifrost-http/handlers/health.go (1)

27-29: LGTM: Clean type migration.

The middleware parameter type correctly migrated from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware, aligning with the PR's centralization goal.

plugins/mocker/main.go (1)

481-484: LGTM: Appropriate no-op implementation.

Returning nil is correct for the mocker plugin since it operates at the PreHook/PostHook level rather than HTTP transport layer. The middleware chain correctly handles nil values (as seen in handlers/middlewares.go line 60).

transports/bifrost-http/lib/middleware.go (1)

11-23: LGTM: Type migration maintains correct chaining logic.

The signature update to use schemas.BifrostHTTPMiddleware is clean and maintains the correct right-to-left construction order for middleware chaining.

docs/plugins/writing-plugin.mdx (1)

65-241: LGTM: Comprehensive documentation for breaking change.

The versioned tabs clearly distinguish v1.3.x from v1.4.x+ patterns, and the HTTPTransportMiddleware section provides appropriate warnings about runtime scope and invocation context. The migration guide is thorough and will help plugin developers adapt to the new middleware pattern.

transports/bifrost-http/handlers/middlewares.go (1)

47-68: LGTM: Correct plugin middleware collection and chaining.

The middleware now properly collects non-nil HTTPTransportMiddleware() results from all plugins and chains them via lib.ChainMiddlewares. The logic correctly handles nil returns (line 60-62) and maintains proper execution order.

plugins/governance/main.go (1)

184-234: LGTM: HTTPTransportMiddleware correctly implements governance logic.

The middleware properly:

  • Parses virtual keys from headers
  • Validates virtual key existence and active status
  • Adds MCP include-tools headers
  • Performs load balancing and provider selection
  • Marshals/unmarshals request body using Sonic

The error handling appropriately continues with next(ctx) on errors, allowing requests to proceed without governance when issues occur.

transports/bifrost-http/server/server.go (1)

958-965: LGTM: Consistent middleware type migration across route registration.

All route registration methods (RegisterInferenceRoutes, RegisterAPIRoutes, RegisterUIRoutes) and PrepareCommonMiddlewares correctly migrated from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware. The middleware ordering at line 1230 appropriately places TransportInterceptorMiddleware first to ensure plugin HTTP transport middleware executes before other inference middlewares.

Also applies to: 968-1051, 1054-1057, 1086-1097

plugins/jsonparser/main.go (1)

86-89: The nil return value is safely handled—nil middleware is filtered out before invocation.

Looking at the middleware chain construction in transports/bifrost-http/handlers/middlewares.go, the code checks each plugin's middleware before using it:

middleware := plugin.HTTPTransportMiddleware()
if middleware == nil {
    continue  // Nil values are skipped, not added to the chain
}
pluginsMiddlewareChain = append(pluginsMiddlewareChain, middleware)

Only non-nil middlewares are added to pluginsMiddlewareChain, so nil function pointers never reach invocation. This is a well-established pattern used by 7 other plugins (telemetry, logging, maxim, mocker, otel, semanticcache, governance) with the same "is not used for this plugin" comment. The implementation is safe and requires no changes.

Likely an incorrect or invalid review comment.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from d632297 to bfb02bb Compare December 23, 2025 02:14
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

♻️ Duplicate comments (4)
examples/plugins/hello-world/main.go (1)

19-26: Note: Existing issue flagged in previous review.

The middleware doesn't set the context value that PreHook/PostHook expect to read (line 29, 38-39). This has already been flagged in a previous review.

plugins/governance/main.go (2)

158-181: Missing prefix validation for x-bf-vk header.

The x-bf-vk header (lines 161-163) is returned without validating the VirtualKeyPrefix, while the Authorization (line 168) and x-api-key (line 177) headers have this validation. This inconsistency was flagged in a previous review.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
+			return bifrost.Ptr(vkValue)
+		}
 	}

212-217: Fix error messages to reflect unmarshal operation.

Lines 214 and 226 both log "failed to marshal" but the operations are sonic.Unmarshal (line 212) and sonic.Marshal (line 224) respectively. Line 214's message should say "unmarshal".

🔎 Proposed fix
-			p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+			p.logger.Error("failed to unmarshal request body: %v", err)
framework/plugins/dynamicplugin.go (1)

140-147: Type mismatch between dynamic plugin loading and example plugin export still present.

The example plugin at examples/plugins/hello-world/main.go:18-25 exports HTTPTransportMiddleware as a factory function func() schemas.BifrostHTTPMiddleware, but line 145 casts directly to schemas.BifrostHTTPMiddleware. This type assertion will fail at runtime.

Either:

  1. Update this code to cast to func() schemas.BifrostHTTPMiddleware and invoke it (consistent with the GetName pattern on line 137), or
  2. Update the example plugin to export the middleware directly instead of a factory function.
🔎 Proposed fix (Option 1: align with factory pattern)
 	// Looking up for HTTPTransportMiddleware method
 	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
 	if err != nil {
 		return nil, err
 	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
 	}
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()
🧹 Nitpick comments (6)
core/bifrost.go (2)

70-71: Tracer initialization and retry span wiring are sound

The new tracer field on Bifrost and initialization in Init (using config.Tracer or schemas.DefaultTracer()) guarantee a non‑nil tracer for downstream use, and passing it into executeRequestWithRetries centralizes span creation for all provider calls. The generic retry helper correctly:

  • Records retry count in context and attributes,
  • Wraps each attempt in its own span (llm.call for first, retry.attempt.N for retries),
  • Attaches provider/model/requestType metadata and error/status details.

One behavioral nuance: because *ctx is updated with the span context each time, later attempts become children of the previous attempt’s span (nested retries). If you’d prefer all attempts as siblings under the same parent span, you could preserve the original parent span ID and avoid chaining through the previous attempt’s span, but the current behavior is still trace‑valid.

Also applies to: 108-127, 2729-2837


2283-2311: Fallback, key-selection, and plugin hook tracing are coherent; only minor hierarchy nits

The added spans around:

  • Fallback attempts (fallback.<provider>.<model> in both unary and streaming paths),
  • Key selection (key.selection in requestWorker), and
  • Per‑plugin PreHook/PostHook executions,

are all well-scoped and tagged (provider, model, fallback index, plugin name, errors). They also correctly propagate the active span ID via BifrostContextKeySpanID so downstream operations become children of the right span.

Two minor, optional refinements you could consider:

  • For key selection, you currently end the key.selection span before updating req.Context to keySpanCtx, so subsequent LLM spans become children of a finished span. That’s valid for tracing, but if you want a cleaner hierarchy you could:

    • Set req.Context = keySpanCtx before calling selectKeyFromProviderForModel, and
    • Keep the parent span as the outer request or root span instead of making the LLM call a child of the key-selection span.
  • In plugin hooks, you set a new span ID in the plugin context but only sync back to the parent context after all hooks via GetParentCtxWithUserValues(). If you ever need per-plugin child spans to be visible immediately to nested operations, you might want to push span IDs into the parent context earlier, though the current approach is consistent and safe.

No functional bugs here; these are purely trace-structure considerations.

Also applies to: 2381-2408, 2896-2924, 3157-3212, 3221-3247, 3267-3271

core/schemas/plugin.go (1)

4-8: Update Plugin execution-order docs to match HTTPTransportMiddleware

The interface and BifrostHTTPMiddleware type correctly move transport interception to a middleware model, and ObservabilityPlugin is well-documented. The comment block above Plugin still describes step 1 as TransportInterceptor though the method is now HTTPTransportMiddleware() returning BifrostHTTPMiddleware. To avoid confusion for plugin authors, consider updating that execution-order description to reference HTTPTransportMiddleware and the HTTP middleware chain rather than the old TransportInterceptor terminology. No runtime issues here.

Also applies to: 36-39, 47-79, 109-137

framework/tracing/propagation.go (1)

10-22: W3C trace-context helpers look correct; consider clarifying ExtractParentID naming

The W3C parsing/validation helpers (ParseTraceparent, FormatTraceparent, InjectTraceContext) are defensively implemented and match the standard version-traceid-parentid-traceflags format.

One minor naming nit: ExtractParentID currently returns ctx.TraceID (the upstream trace ID) rather than ctx.ParentID (the parent span ID). The comment says “parent trace ID”, and you pass this into CreateTrace(parentID) in TracingMiddleware, so the behavior is consistent, but the function name and struct field names can be confusing. Consider either:

  • Renaming it to something like ExtractUpstreamTraceID, or
  • Returning both trace ID and parent span ID via ExtractTraceContext where you already expose both.

Functionally this is fine; this is just about avoiding future misunderstandings.

Also applies to: 24-37, 38-95, 97-117, 119-127

plugins/otel/converter.go (2)

165-170: Potential overflow when converting large uint64 values to int64.

Lines 169-170 convert uint64 to int64 which will overflow for values greater than math.MaxInt64 (~9.2 quintillion). While this is unlikely for typical use cases, it could silently produce incorrect negative values for very large unsigned integers stored in span attributes.

🔎 Suggested handling for large uint64
 	case uint64:
+		if v > uint64(^uint(0)>>1) { // v > MaxInt64
+			return kvStr(key, fmt.Sprintf("%d", v))
+		}
 		return kvInt(key, int64(v))

68-69: Silently ignored error from hex.DecodeString.

While the input is sanitized and padded, explicitly handling the error would be more defensive. The error is discarded with bytes, _ := hex.DecodeString(cleaned).

🔎 Optional: log or return empty on error
-	bytes, _ := hex.DecodeString(cleaned)
-	return bytes
+	bytes, err := hex.DecodeString(cleaned)
+	if err != nil {
+		return make([]byte, length)
+	}
+	return bytes
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d632297 and bfb02bb.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (55)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/go.mod
  • core/mcp.go
  • core/schemas/bifrost.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/components/sidebar.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (2)
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • core/mcp.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/ui.go
  • docs/plugins/migration-guide.mdx
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/integrations.go
  • docs/plugins/writing-plugin.mdx
  • transports/go.mod
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/health.go
  • ui/components/sidebar.tsx
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/main.go
  • plugins/logging/main.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • core/go.mod
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/session.go
  • core/schemas/bifrost.go
  • plugins/jsonparser/main.go
  • framework/tracing/propagation.go
  • plugins/maxim/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/lib/middleware.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/cache.go
  • framework/tracing/helpers.go
  • examples/plugins/hello-world/go.mod
  • framework/tracing/tracer.go
  • core/schemas/plugin.go
  • core/schemas/tracer.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/config.go
  • ui/components/ui/switch.tsx
  • examples/plugins/hello-world/main.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/schemas/trace.go
  • plugins/otel/converter.go
  • docs/docs.json
  • framework/tracing/llmspan.go
  • plugins/governance/main.go
  • core/bifrost.go
  • docs/plugins/getting-started.mdx
🧠 Learnings (3)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/session.go
  • core/schemas/bifrost.go
  • plugins/jsonparser/main.go
  • framework/tracing/propagation.go
  • plugins/maxim/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/lib/middleware.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/cache.go
  • framework/tracing/helpers.go
  • framework/tracing/tracer.go
  • core/schemas/plugin.go
  • core/schemas/tracer.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/config.go
  • examples/plugins/hello-world/main.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/schemas/trace.go
  • plugins/otel/converter.go
  • framework/tracing/llmspan.go
  • plugins/governance/main.go
  • core/bifrost.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/middlewares_test.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
🧬 Code graph analysis (27)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (1)
core/schemas/tracer.go (1)
  • Tracer (13-30)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/server/server.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (1)
  • TransportInterceptorMiddleware (49-69)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/store.go (2)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/tracer.go (5)
framework/tracing/store.go (1)
  • TraceStore (13-23)
core/schemas/trace.go (4)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
core/schemas/tracer.go (2)
  • SpanHandle (8-8)
  • Tracer (13-30)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (1)
  • BifrostContextKeySpanID (144-144)
core/schemas/plugin.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/tracer.go (2)
core/schemas/trace.go (2)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
plugins/otel/main.go (3)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (31-41)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/bifrost_test.go (1)
core/schemas/tracer.go (1)
  • DefaultTracer (51-53)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (163-163)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (16)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
core/bifrost.go (1)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
🔇 Additional comments (39)
ui/components/ui/switch.tsx (1)

16-16: Confirm the switch design change from pill-shaped to slightly rounded corners.

Standard implementations like Radix UI use rounded-full for both the Root and Thumb of switch components, creating the characteristic pill-shaped appearance. Changing to rounded-sm (2px border-radius) introduces a visual breaking change that deviates from established UI patterns. Ensure this design direction aligns with the broader UI system redesign and document this change in migration notes so users understand the visual impact.

core/go.mod (1)

27-27: LGTM! Routine dependency updates.

The addition of semver/v3 and update to pprof are indirect dependencies, likely pulled in transitively by the new plugin architecture changes. These updates appear routine and appropriate for the PR's scope.

Also applies to: 50-50

docs/plugins/getting-started.mdx (1)

51-103: LGTM! Excellent version-specific documentation.

The use of tabs to document both v1.4.x+ (with HTTPTransportMiddleware) and v1.3.x (with TransportInterceptor) provides a clear migration path for users. The execution order is properly documented for each version, making the breaking change easy to understand.

docs/docs.json (1)

135-136: LGTM! Essential migration guide added.

Adding the migration guide to the documentation navigation is appropriate for a breaking change. The placement after "writing-plugin" makes logical sense for users exploring plugin development.

transports/bifrost-http/handlers/config.go (1)

15-15: LGTM! Consistent middleware type migration.

The change from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware aligns with the PR's goal of consolidating middleware types in the core schemas package. This is part of the broader migration to the new plugin architecture.

Also applies to: 55-55

transports/bifrost-http/handlers/providers.go (1)

80-80: LGTM! Consistent middleware type migration.

The middleware type change matches the pattern applied across all handler files, maintaining consistency with the new plugin architecture.

transports/bifrost-http/handlers/logging.go (1)

42-42: LGTM! Consistent middleware type migration.

The change maintains consistency with the middleware type migration applied across all handler files in this PR.

transports/bifrost-http/handlers/middlewares_test.go (1)

308-308: LGTM! Comprehensive test coverage maintained.

All test cases have been consistently updated to use schemas.BifrostHTTPMiddleware instead of lib.BifrostHTTPMiddleware. The test logic remains unchanged, ensuring that middleware chaining, execution order, context modification, and short-circuit behavior continue to be properly verified.

Also applies to: 335-335, 342-342, 349-349, 381-381, 408-408, 418-418, 426-426, 471-471, 480-480, 490-490

core/bifrost_test.go (1)

71-71: LGTM! Tests updated for tracer parameter.

All test cases have been consistently updated to pass schemas.DefaultTracer() to executeRequestWithRetries, aligning with the new signature that supports tracing. The use of DefaultTracer() (a no-op tracer) is appropriate for unit tests, maintaining test isolation while exercising the new tracing parameter.

Also applies to: 105-105, 139-139, 202-202, 275-275, 504-504

transports/bifrost-http/handlers/cache.go (1)

26-29: LGTM! Middleware type migration is correct.

The RegisterRoutes signature has been properly updated to use schemas.BifrostHTTPMiddleware instead of lib.BifrostHTTPMiddleware, aligning with the new centralized middleware type definition in core/schemas.

transports/bifrost-http/handlers/mcpServer.go (1)

72-76: LGTM! Middleware type migration is consistent.

The RegisterRoutes signature correctly adopts schemas.BifrostHTTPMiddleware, maintaining consistency with the framework-wide middleware migration.

transports/bifrost-http/handlers/session.go (1)

32-36: LGTM! Middleware type update is correct.

RegisterRoutes properly migrated to use schemas.BifrostHTTPMiddleware with no functional changes.

core/schemas/bifrost.go (2)

25-25: LGTM! Tracer integration is well-designed.

The optional Tracer field with documented nil-as-NoOp behavior allows distributed tracing to be integrated without breaking existing code. This follows good design patterns for optional observability features.


143-144: LGTM! Trace context keys follow established conventions.

The new BifrostContextKeyTraceID and BifrostContextKeySpanID constants are properly typed and follow the existing BifrostContextKey naming convention, enabling trace propagation through the Bifrost context.

framework/plugins/dynamicplugin_test.go (1)

54-84: LGTM! Test correctly validates new middleware pattern.

The updated test properly exercises the HTTPTransportMiddleware() method by:

  • Creating a mock next handler with a flag to track invocation
  • Obtaining and wrapping the middleware function
  • Verifying the wrapped handler correctly calls the next handler

This test implementation correctly validates the new middleware contract.

transports/bifrost-http/handlers/governance.go (1)

154-175: LGTM! Governance routes properly migrated to new middleware type.

RegisterRoutes correctly updated to use schemas.BifrostHTTPMiddleware, maintaining consistency with the repository-wide middleware migration.

examples/plugins/hello-world/go.mod (1)

6-23: fasthttp v1.67.0 is secure for use.

The version includes security improvements for URL validation and IPv6 address validation, and no known vulnerabilities were identified for this release. The addition is appropriate for the new HTTP middleware pattern.

plugins/telemetry/main.go (1)

279-282: No action needed. The telemetry plugin correctly returns nil from HTTPTransportMiddleware() since this plugin does not require HTTP transport-level interception. The middleware chain collection logic in transports/bifrost-http/handlers/middlewares.go explicitly filters out nil middleware functions before passing them to ChainMiddlewares, preventing any nil pointer dereferences. This is a safe and intentional pattern.

Likely an incorrect or invalid review comment.

plugins/semanticcache/main.go (1)

338-341: LGTM!

The no-op implementation is correct for this plugin, which doesn't require HTTP transport middleware.

transports/bifrost-http/lib/middleware.go (1)

3-6: LGTM!

The migration to the centralized schemas.BifrostHTTPMiddleware type is correct and maintains backward compatibility.

Also applies to: 11-11

plugins/maxim/main.go (1)

124-127: LGTM!

The no-op implementation is appropriate for this plugin, which doesn't require HTTP transport middleware.

core/schemas/tracer.go (1)

1-57: LGTM!

The Tracer interface is well-designed with a minimal surface area. The NoOpTracer provides a safe default, and the compile-time interface check ensures correctness.

plugins/jsonparser/main.go (1)

86-89: LGTM!

The no-op implementation is correct for this plugin, which processes responses in PostHook rather than at the transport layer.

framework/tracing/store.go (1)

1-302: LGTM!

The TraceStore implementation is well-designed with proper:

  • Object pooling to reduce allocations
  • Thread-safe concurrent access via sync.Map
  • TTL-based cleanup with graceful shutdown
  • Pre-allocated capacities for slices to minimize reallocations
transports/bifrost-http/server/server.go (1)

958-965: Middleware type migration to schemas.BifrostHTTPMiddleware looks consistent

All server route registration and middleware preparation now consistently use schemas.BifrostHTTPMiddleware, and inference middlewares correctly prepend TransportInterceptorMiddleware(s.Config) while preserving existing behavior. No issues spotted here.

Also applies to: 968-1057, 1086-1097, 1220-1234

transports/bifrost-http/handlers/middlewares.go (1)

20-46: HTTP middleware type changes and new tracing middleware integrate cleanly

  • CorsMiddleware, AuthMiddleware, and TransportInterceptorMiddleware now correctly expose schemas.BifrostHTTPMiddleware, matching the new Plugin contract. TransportInterceptorMiddleware safely skips plugins that return nil middleware and chains the rest via lib.ChainMiddlewares without altering ordering semantics.
  • TracingMiddleware cleanly wires request‑scoped traces:
    • Derives a parent ID from W3C trace headers and calls store.CreateTrace(parentID).
    • Stores the resulting trace ID and root span ID in RequestCtx user values for downstream components.
    • Ends the root span with an OK or error status based on the HTTP response code.
    • Asynchronously completes the trace, forwards it to all non‑nil observability plugins via Inject, and then releases it back to the store.

This is a solid foundation for HTTP‑level tracing; any further enhancements (e.g., sampling or additional HTTP attributes) can be layered on later without changing the API.

Also applies to: 48-69, 87-188, 190-264, 266-281

framework/tracing/tracer.go (1)

1-21: StoreTracer correctly bridges schemas.Tracer to TraceStore

StoreTracer cleanly implements schemas.Tracer: it:

  • Reads the current trace ID and optional parent span ID from context,
  • Starts either root or child spans and propagates the new span ID back into context,
  • Safely no‑ops when there’s no trace ID or when the handle is nil/invalid,
  • Updates attributes and events via TraceStore and Span methods.

As long as callers only construct StoreTracer with a non‑nil *TraceStore, this provides a solid bridge between the core tracer interface and the in‑memory trace store.

Also applies to: 23-29, 30-57, 59-67, 69-82, 84-102, 104-106

framework/tracing/helpers.go (1)

1-21: Tracing helper functions are straightforward and defensive

The helpers (GetTraceID, GetTrace, AddSpan, AddChildSpan, EndSpan, SetSpanAttribute, AddSpanEvent) provide a clean, minimal API over TraceStore, with appropriate nil/empty checks for context, trace, and span. They’re safe to use across the codebase and complement the higher-level StoreTracer nicely.

Also applies to: 22-29, 31-38, 40-47, 49-56, 58-69, 71-82

plugins/otel/main.go (1)

55-75: No action required. The code correctly uses strings.SplitSeq, a standard library function added in Go 1.24 that returns an iterator over substrings. This pattern is valid and preferred for iterating over split results without allocating a slice.

Likely an incorrect or invalid review comment.

framework/plugins/dynamicplugin.go (1)

36-39: LGTM for the middleware accessor method.

The HTTPTransportMiddleware() method correctly returns the stored middleware. Since loadDynamicPlugin requires all symbols to be present and properly cast before returning, the field will never be nil when accessed.

plugins/otel/converter.go (2)

72-88: Well-structured trace-to-OTEL conversion.

The convertTraceToResourceSpan function cleanly iterates through spans and assembles a properly structured ResourceSpan with resource attributes and instrumentation scope. The implementation follows OTEL conventions.


231-269: Span kind and status mappings are comprehensive.

Both convertSpanKind and convertSpanStatus cover all defined SpanKind and SpanStatus constants from core/schemas/trace.go, with appropriate defaults for unrecognized values.

core/schemas/trace.go (3)

21-38: Thread-safe span operations look correct.

AddSpan and GetSpan properly acquire the mutex before accessing the shared Spans slice. The linear search in GetSpan is acceptable for typical trace sizes.


67-82: Thread-safe attribute and event operations look correct.

SetAttribute and AddEvent properly acquire the mutex and handle nil map initialization. These follow the expected patterns for concurrent access.


154-325: Comprehensive attribute key definitions following OpenTelemetry conventions.

The extensive set of gen_ai.* namespace attribute keys is well-organized by category and follows OpenTelemetry semantic conventions. Good documentation comments explain the purpose.

framework/tracing/llmspan.go (4)

1118-1132: Message extraction helpers are well-implemented.

extractChatMessages properly handles nil content by skipping such messages. The helper uses a pre-allocated slice with appropriate capacity.


1207-1228: Efficient content extraction with strings.Builder.

extractMessageContent correctly uses strings.Builder for efficient concatenation when processing multiple content blocks.


12-57: Clean dispatcher pattern for request attribute population.

PopulateRequestAttributes provides a well-organized entry point that dispatches to type-specific handlers based on RequestType. The nil check at the start prevents panics.


59-100: Response attribute dispatcher uses switch on pointer fields.

PopulateResponseAttributes uses switch { case resp.X != nil: } pattern which works but differs from the request dispatcher's switch req.RequestType pattern. This is acceptable since BifrostResponse may not have an explicit type field.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from bfb02bb to b8f95ce Compare December 23, 2025 06:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/integrations/router.go (1)

513-518: Context parameter change breaks downstream context value propagation and requires fixing.

The code sets Bifrost-specific context values in bifrostCtx using custom-typed keys (e.g., schemas.BifrostContextKeySendBackRawResponse, schemas.BifrostContextKeyDirectKey) via context.WithValue. However, client methods now receive ctx (fasthttp.RequestCtx) instead of bifrostCtx. Since fasthttp.RequestCtx.Value() only supports string keys, these custom-typed context values set in bifrostCtx will not be accessible to the client methods, breaking features that depend on them (such as direct key selection, raw response handling, and integration-type routing). Pass bifrostCtx to client methods instead of ctx to preserve context value propagation.

core/bifrost.go (1)

2731-2743: Clarify PopulateLLMResponseAttributes’s contract when resp is nil

In executeRequestWithRetries, you always end the span and, for non-streaming calls, invoke:

if resp, ok := any(result).(*schemas.BifrostResponse); ok {
    tracer.PopulateLLMResponseAttributes(handle, resp, bifrostError)
}

When the handler returns an error, result can be a typed (*schemas.BifrostResponse)(nil), so ok is true and resp is nil. Tracer implementations that assume resp is non-nil could panic here.

Either:

  • Guard the call with resp != nil, or
  • Explicitly document in Tracer.PopulateLLMResponseAttributes that resp may be nil while err is non-nil and must be handled defensively.

This keeps tracing backends robust under error paths.

Also applies to: 2789-2817

♻️ Duplicate comments (5)
examples/plugins/hello-world/main.go (1)

19-26: HTTPTransportMiddleware implementation is correct for dynamic plugins.

The free function signature is appropriate for Go's plugin system, where exported symbols are looked up by name and wrapped by the DynamicPlugin adapter.

However, note that PreHook (line 29) and PostHook (lines 38-39) still attempt to read the context value "hello-world-plugin-transport-interceptor", which is no longer set by this middleware. This results in value1 always being nil, breaking the example's context propagation demonstration. This issue was flagged in previous reviews.

plugins/governance/main.go (2)

158-181: Missing prefix validation for x-bf-vk header.

The parseVirtualKey function validates that values from the Authorization (Bearer) and x-api-key headers start with VirtualKeyPrefix (lines 168, 177), but the x-bf-vk header on lines 160-163 is returned without prefix validation. This inconsistency could allow invalid virtual keys to pass through when provided via the x-bf-vk header.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
-		return bifrost.Ptr(string(vkHeader))
+			return bifrost.Ptr(vkValue)
+		}
 	}

211-217: Fix error message accuracy.

Line 214: The error message says "failed to marshal" but the operation is unmarshaling (sonic.Unmarshal).

🔎 Proposed fix
-			p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+			p.logger.Error("failed to unmarshal request body: %v", err)
core/schemas/trace.go (1)

40-49: Guard Trace.Reset, Span.End, and Span.Reset with the existing mutex to avoid races

Trace and Span both embed a mu sync.Mutex, and mutating methods like AddSpan, GetSpan, SetAttribute, and AddEvent use it. However:

  • Trace.Reset clears IDs, spans, and attributes without locking.
  • Span.End updates EndTime, Status, and StatusMsg without locking.
  • Span.Reset clears all fields and slices without locking.

If these methods are ever called while other goroutines still hold references obtained via GetTrace/GetSpan (or via the store), this can lead to data races despite the presence of mu.

Given you already rely on the mutex for other mutations, it would be safer and more consistent to wrap these methods with the same lock, e.g.:

func (t *Trace) Reset() {
-   t.TraceID = ""
-   t.ParentID = ""
-   t.RootSpan = nil
-   t.Spans = t.Spans[:0]
-   t.StartTime = time.Time{}
-   t.EndTime = time.Time{}
-   t.Attributes = nil
+   t.mu.Lock()
+   defer t.mu.Unlock()
+   t.TraceID = ""
+   t.ParentID = ""
+   t.RootSpan = nil
+   t.Spans = t.Spans[:0]
+   t.StartTime = time.Time{}
+   t.EndTime = time.Time{}
+   t.Attributes = nil
}

and similarly for Span.End and Span.Reset.

This keeps span/trace lifecycle mutations consistent with the rest of the API and avoids subtle races when pooling is combined with concurrent readers.

Also applies to: 84-104

framework/tracing/llmspan.go (1)

190-193: ExtraParams written directly to attrs map may cause attribute key collisions.

This issue was previously flagged: user-provided keys from ExtraParams are written directly to the attributes map without namespacing. If a user sets a key like "gen_ai.request.model" in ExtraParams, it would overwrite the standard attribute set on line 22.

This pattern repeats throughout the file at lines 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029.

🔎 Suggested approach: namespace ExtraParams
 	// ExtraParams
 	for k, v := range req.Params.ExtraParams {
-		attrs[k] = fmt.Sprintf("%v", v)
+		attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 	}

Apply this pattern consistently at all ExtraParams locations throughout the file.

🧹 Nitpick comments (3)
ui/components/ui/sheet.tsx (1)

103-103: Simplify redundant mb-6 application.

The base className already includes mb-6, and the conditional also applies mb-6 when expandable is false, resulting in duplicate class application. While Tailwind handles this gracefully, the code can be simplified for clarity.

🔎 Suggested simplification
-<div data-slot="sheet-header" className={cn("mb-6 flex items-center", sheetContext?.expandable ? "p-0" : "mb-6")} {...props}>
+<div data-slot="sheet-header" className={cn("flex items-center", sheetContext?.expandable ? "p-0" : "mb-6")} {...props}>
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

181-201: Add aria-label to the action menu trigger for better accessibility.

The dropdown menu implementation follows best practices by consolidating actions into an overflow menu. However, the icon-only trigger button should include an aria-label to improve screen reader accessibility.

🔎 Suggested improvement
 <DropdownMenuTrigger asChild>
-  <Button variant="ghost" size="icon">
+  <Button variant="ghost" size="icon" aria-label="Log actions">
     <MoreVertical className="h-3 w-3" />
   </Button>
 </DropdownMenuTrigger>
framework/tracing/helpers.go (1)

10-29: Helpers are sound; optionally guard against nil TraceStore

The helper layer around TraceStore (GetTraceID, GetTrace, AddSpan*, EndSpan, SetSpanAttribute, AddSpanEvent) is clean and includes good nil/empty-traceID checks, so they safely no-op when there is no active trace.

If you ever expect these to be called before a tracer is fully wired, you might want an extra defensive if store == nil { return } in the helpers that dereference store to avoid panics from accidental nil stores. Right now, they assume the tracer always passes a non-nil store, which is OK but a bit brittle.

Also applies to: 49-82

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bfb02bb and b8f95ce.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (57)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/go.mod
  • core/mcp.go
  • core/schemas/bifrost.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (2)
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (1)
  • docs/plugins/migration-guide.mdx
🚧 Files skipped from review as they are similar to previous changes (17)
  • core/mcp.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcpServer.go
  • plugins/mocker/main.go
  • plugins/telemetry/main.go
  • plugins/governance/go.mod
  • plugins/maxim/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/go.mod
  • core/bifrost_test.go
  • plugins/semanticcache/main.go
  • ui/components/sidebar.tsx
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/websocket.go
  • ui/components/ui/switch.tsx
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/config.go
  • core/go.mod
  • transports/bifrost-http/handlers/session.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • core/schemas/bifrost.go
  • docs/plugins/getting-started.mdx
  • core/schemas/plugin.go
  • ui/components/ui/sheet.tsx
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/providers.go
  • docs/plugins/writing-plugin.mdx
  • core/schemas/tracer.go
  • framework/tracing/store.go
  • transports/bifrost-http/server/server.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/health.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/plugins/dynamicplugin.go
  • core/bifrost.go
  • plugins/otel/converter.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • examples/plugins/hello-world/main.go
  • plugins/jsonparser/main.go
  • framework/tracing/tracer.go
  • core/schemas/trace.go
  • framework/tracing/propagation.go
  • framework/tracing/helpers.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/integrations.go
  • docs/docs.json
  • framework/tracing/llmspan.go
🧠 Learnings (3)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/session.go
  • core/schemas/bifrost.go
  • core/schemas/plugin.go
  • plugins/governance/main.go
  • transports/bifrost-http/handlers/providers.go
  • core/schemas/tracer.go
  • framework/tracing/store.go
  • transports/bifrost-http/server/server.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/health.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/plugins/dynamicplugin.go
  • core/bifrost.go
  • plugins/otel/converter.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • examples/plugins/hello-world/main.go
  • plugins/jsonparser/main.go
  • framework/tracing/tracer.go
  • core/schemas/trace.go
  • framework/tracing/propagation.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/integrations.go
  • framework/tracing/llmspan.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/integrations.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
🧬 Code graph analysis (27)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/app/workspace/logs/views/logDetailsSheet.tsx (3)
ui/components/ui/sheet.tsx (1)
  • SheetContent (137-137)
ui/components/ui/alertDialog.tsx (9)
  • AlertDialog (83-83)
  • AlertDialogTrigger (93-93)
  • AlertDialogContent (86-86)
  • AlertDialogHeader (89-89)
  • AlertDialogTitle (92-92)
  • AlertDialogDescription (87-87)
  • AlertDialogFooter (88-88)
  • AlertDialogCancel (85-85)
  • AlertDialogAction (84-84)
ui/components/ui/dropdownMenu.tsx (5)
  • DropdownMenu (193-193)
  • DropdownMenuTrigger (207-207)
  • DropdownMenuContent (195-195)
  • DropdownMenuItem (197-197)
  • DropdownMenuSeparator (202-202)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (13-47)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/tracer.go (4)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/schemas/bifrost.go (3)
  • BifrostRequest (168-188)
  • BifrostResponse (330-350)
  • BifrostError (469-478)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
transports/bifrost-http/server/server.go (4)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
transports/bifrost-http/handlers/middlewares.go (2)
  • TransportInterceptorMiddleware (50-70)
  • NewTracingMiddleware (207-214)
framework/tracing/store.go (1)
  • NewTraceStore (26-53)
framework/tracing/tracer.go (1)
  • NewTracer (19-21)
plugins/otel/main.go (3)
core/schemas/plugin.go (1)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (31-41)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/logging/main.go (5)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/lib/types/logs.ts (1)
  • TranscriptionInput (15-17)
core/schemas/bifrost.go (1)
  • TranscriptionRequest (102-102)
transports/bifrost-http/handlers/inference.go (1)
  • TranscriptionRequest (285-289)
transports/bifrost-http/handlers/middlewares.go (6)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
core/schemas/tracer.go (1)
  • Tracer (13-47)
framework/tracing/propagation.go (1)
  • ExtractParentID (26-36)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/bifrost.go (4)
core/schemas/tracer.go (2)
  • Tracer (13-47)
  • DefaultTracer (80-82)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
core/schemas/trace.go (3)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • SpanKind (115-115)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/tracer.go (6)
framework/tracing/store.go (1)
  • TraceStore (13-23)
core/schemas/tracer.go (2)
  • Tracer (13-47)
  • SpanHandle (8-8)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (4)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (168-188)
  • BifrostResponse (330-350)
  • BifrostError (469-478)
framework/tracing/llmspan.go (3)
  • PopulateRequestAttributes (14-60)
  • PopulateResponseAttributes (64-106)
  • PopulateErrorAttributes (109-124)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (163-163)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (13-23)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (7)
  • ListModelsRequest (92-92)
  • TextCompletionRequest (93-93)
  • ChatCompletionRequest (95-95)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/llmspan.go (6)
core/schemas/bifrost.go (18)
  • BifrostRequest (168-188)
  • RequestType (89-89)
  • TextCompletionRequest (93-93)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • SpeechRequest (100-100)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (330-350)
core/schemas/trace.go (24)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (208-208)
  • AttrErrorType (209-209)
  • AttrErrorCode (210-210)
  • AttrVirtualKeyID (220-220)
  • AttrVirtualKeyName (221-221)
  • AttrSelectedKeyID (222-222)
  • AttrSelectedKeyName (223-223)
  • AttrTeamID (224-224)
  • AttrTeamName (225-225)
  • AttrCustomerID (226-226)
  • AttrCustomerName (227-227)
  • AttrNumberOfRetries (228-228)
  • AttrFallbackIndex (229-229)
  • AttrMaxTokens (163-163)
  • AttrTemperature (164-164)
  • AttrTopP (165-165)
  • AttrStopSequences (166-166)
  • AttrPresencePenalty (167-167)
  • AttrFrequencyPenalty (168-168)
  • AttrParallelToolCall (169-169)
  • AttrRequestUser (170-170)
  • AttrN (175-175)
core/schemas/chatcompletions.go (1)
  • BifrostChatRequest (12-19)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (32-39)
core/schemas/files.go (6)
  • BifrostFileUploadRequest (53-67)
  • BifrostFileListRequest (109-128)
  • BifrostFileRetrieveRequest (146-159)
  • BifrostFileDeleteRequest (186-198)
  • BifrostFileContentRequest (215-227)
  • BifrostFileUploadResponse (90-106)
framework/modelcatalog/main.go (1)
  • ModelCatalog (23-44)
🔇 Additional comments (53)
examples/plugins/hello-world/go.mod (2)

7-7: Verify core dependency version reflects breaking changes.

The example plugin depends on github.com/maximhq/bifrost/core v1.2.40. Given the breaking change (replacing TransportInterceptor with HTTPTransportMiddleware), confirm this core version includes the new interface and that the hello-world plugin's implementation aligns with it.

Also verify that the reorganization of direct and indirect dependencies is intentional and consistent with go mod tidy output (rather than manually maintained).

Also applies to: 11-23


8-8: No changes needed. The fasthttp v1.67.0 version in hello-world/go.mod is correct and consistent with the core module v1.2.40. The HTTPTransportMiddleware implementation in main.go properly uses the fasthttp.RequestHandler pattern, and all transitive dependencies (brotli, compress, bytebufferpool) are expected from fasthttp. Versions are aligned across the plugin stack.

ui/app/workspace/logs/views/logDetailsSheet.tsx (4)

16-16: LGTM!

The new imports for dropdown menu components and the MoreVertical icon are appropriate for the refactored action menu pattern.

Also applies to: 22-22


202-218: LGTM!

The AlertDialog implementation for delete confirmation is well-structured. The dialog properly handles the destructive action with clear messaging, cancel option, and correctly closes the sheet after deletion.


171-171: Remove the expandable prop verification concern.

The expandable prop is properly implemented as a custom feature in the SheetContent component. It has a default value of false, is integrated into the component's context, and controls responsive width and styling behavior. No changes needed.

Likely an incorrect or invalid review comment.


195-195: The destructive variant on DropdownMenuItem is already properly implemented in the wrapper component with full type safety and appropriate styling for light/dark modes and focus states. No changes needed.

core/go.mod (1)

27-27: LGTM: Routine indirect dependency updates.

The addition of semver/v3 and the update to google/pprof appear to be indirect dependency changes, likely pulled in by the new tracing infrastructure or other updated dependencies in this PR.

Also applies to: 50-50

core/schemas/bifrost.go (2)

25-25: LGTM: Well-documented tracing support.

The addition of the Tracer field to BifrostConfig with clear documentation about the nil = NoOpTracer behavior is excellent. This provides a clean opt-in mechanism for distributed tracing.


143-144: LGTM: Tracing context keys follow established patterns.

The new context keys for trace ID and span ID follow the existing BifrostContextKey naming convention and enable proper trace propagation through the request lifecycle. The comments clearly indicate these are set by the tracing middleware and tracer respectively.

docs/docs.json (1)

135-136: LGTM: Essential documentation for breaking changes.

Adding the migration guide to the Custom plugins section is crucial for this breaking change. The placement after "writing-plugin" makes logical sense for developers who need to update their plugins from v1.3.x to v1.4.x.

transports/bifrost-http/handlers/providers.go (1)

80-80: LGTM: Middleware type migration aligns with plugin v2 architecture.

The migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware centralizes the middleware type definition in the core schemas package. This change is consistent with the broader plugin v2 architecture migration and maintains the same functional signature.

transports/bifrost-http/handlers/plugins.go (1)

53-53: LGTM: Consistent middleware type migration.

This change mirrors the migration pattern seen across all handler files, updating the middleware type to use the centralized schemas.BifrostHTTPMiddleware definition.

transports/bifrost-http/handlers/integrations.go (1)

8-8: LGTM: Integration handler updated for middleware type migration.

The addition of the schemas import and the update to use schemas.BifrostHTTPMiddleware maintains consistency with the rest of the HTTP transport layer. The middleware forwarding logic to extensions remains unchanged.

Also applies to: 37-37

transports/bifrost-http/handlers/ui.go (1)

11-11: LGTM: UI handler completes the middleware type migration.

This change completes the systematic migration across all handler files. The UI serving logic remains unchanged while the middleware type is updated to the centralized schemas.BifrostHTTPMiddleware.

Also applies to: 29-29

plugins/logging/main.go (1)

196-199: Returning nil for HTTPTransportMiddleware() is correct since the logging plugin only uses PreHook and PostHook for request/response logging.

The framework properly handles nil middleware returns. The plugin loader explicitly checks for nil middleware at line 62 in transports/bifrost-http/handlers/middlewares.go and skips them, ensuring only active middlewares are passed to the chaining logic.

transports/bifrost-http/handlers/session.go (1)

11-11: LGTM! Clean migration to schemas.BifrostHTTPMiddleware.

The import and signature change correctly align with the new middleware type defined in core/schemas/plugin.go. The routing logic remains unchanged, maintaining backward compatibility at the handler level.

Also applies to: 32-32

transports/bifrost-http/handlers/config.go (1)

15-15: LGTM! Consistent middleware type migration.

The signature update follows the same pattern as other handlers in this PR, correctly using the new schemas.BifrostHTTPMiddleware type.

Also applies to: 55-55

transports/bifrost-http/handlers/health.go (1)

9-9: LGTM! Handler signature properly updated.

The middleware type migration is consistent with the broader refactor across all HTTP handlers.

Also applies to: 27-27

framework/plugins/dynamicplugin_test.go (1)

16-16: LGTM! Test properly validates the new middleware pattern.

The test correctly:

  • Creates a mock next handler to track invocation
  • Obtains the middleware function from the plugin
  • Wraps the mock handler with the middleware
  • Verifies the middleware calls the next handler in the chain

This validates the middleware chaining semantics introduced by the new architecture.

Also applies to: 54-84

transports/bifrost-http/handlers/governance.go (1)

15-15: LGTM! Governance handler signature updated consistently.

The middleware type migration aligns with the unified schemas.BifrostHTTPMiddleware pattern used across all HTTP handlers.

Also applies to: 154-154

docs/plugins/getting-started.mdx (1)

51-68: LGTM! Excellent documentation of the breaking change.

The versioned tabs clearly distinguish the v1.4.x+ API (with HTTPTransportMiddleware) from the v1.3.x API (with TransportInterceptor), helping plugin developers understand the migration path. The execution order is properly documented for both versions.

Also applies to: 90-103

core/schemas/plugin.go (3)

36-38: LGTM! Clean middleware type definition.

The BifrostHTTPMiddleware type follows the standard HTTP middleware pattern: a function that takes the next handler and returns a new handler. This is more idiomatic than the previous TransportInterceptor approach.


75-79: LGTM! Plugin interface properly migrated to middleware pattern.

The replacement of TransportInterceptor with HTTPTransportMiddleware() BifrostHTTPMiddleware provides a cleaner, more composable API for HTTP transport interception. The method signature is consistent with standard middleware patterns used in Go HTTP frameworks.


109-137: LGTM! ObservabilityPlugin interface enables async trace forwarding.

The new interface properly extends Plugin and adds the Inject method for receiving completed traces. The documentation clearly explains:

  • Async invocation after response is written (no client latency impact)
  • Usage via Go type assertion
  • Expected backend implementations (OTEL, Datadog, Jaeger, etc.)

This is a well-designed extension point for observability integrations.

transports/bifrost-http/integrations/router.go (1)

72-72: Middleware type migration looks correct.

The interface and method signatures are updated to use schemas.BifrostHTTPMiddleware instead of lib.BifrostHTTPMiddleware, aligning with the centralized type definition in core/schemas/plugin.go. This is consistent across the codebase migration in this PR.

Also applies to: 320-320

docs/plugins/writing-plugin.mdx (1)

65-126: Documentation for v1.4.x+ HTTPTransportMiddleware is clear and comprehensive.

The new tab structure with side-by-side v1.3.x and v1.4.x+ examples provides excellent migration guidance. The HTTPTransportMiddleware example correctly demonstrates the middleware pattern with next(ctx) invocation.

One minor observation: The example shows request modification before next(ctx) and response modification after, which is good practice to document.

plugins/jsonparser/main.go (1)

86-89: HTTPTransportMiddleware returning nil is appropriate for this plugin.

Since the JSON parser plugin only processes responses (in PostHook), returning nil from HTTPTransportMiddleware is correct. The middleware chain in lib.ChainMiddlewares properly skips nil middlewares.

plugins/governance/main.go (1)

183-234: HTTPTransportMiddleware implementation looks solid.

The middleware correctly:

  • Parses virtual keys from multiple header sources
  • Gracefully degrades on errors by calling next(ctx) rather than blocking requests
  • Modifies headers and body appropriately using fasthttp APIs
  • Follows the middleware pattern with next(ctx) invocation
transports/bifrost-http/lib/middleware.go (1)

3-23: Clean migration to centralized middleware type.

The ChainMiddlewares function now uses schemas.BifrostHTTPMiddleware from the core schemas package instead of a local type definition. The chaining logic (right-to-left composition for left-to-right execution) remains unchanged and correct.

framework/tracing/store.go (2)

56-82: Object pooling implementation looks correct.

The CreateTrace method properly resets pooled objects before reuse:

  • Generates new TraceID
  • Resets time fields
  • Clears RootSpan
  • Resets slices while preserving capacity (efficient)
  • Clears attributes map

This prevents data leakage between pooled objects.


120-165: Span lifecycle management is well implemented.

The StartSpan method properly:

  • Gets span from pool
  • Resets all fields (including events and attributes)
  • Sets parent relationship based on root span
  • Adds span to trace

The parent tracking logic (lines 154-159) correctly handles both root spans and child spans.

plugins/otel/main.go (2)

201-224: Inject method implementation is clean and correct.

The Inject method properly:

  • Handles nil trace gracefully
  • Checks client initialization
  • Converts trace to OTEL ResourceSpan format
  • Emits to collector with error handling
  • Logs errors but returns them for upstream handling

This aligns well with the ObservabilityPlugin interface contract described in core/schemas/plugin.go.


237-238: Good use of compile-time interface check.

The var _ schemas.ObservabilityPlugin = (*OtelPlugin)(nil) pattern ensures the plugin correctly implements the interface at compile time, catching signature mismatches early.

transports/bifrost-http/handlers/middlewares.go (4)

49-70: TransportInterceptorMiddleware correctly collects and chains plugin middlewares.

The implementation:

  • Collects non-nil middlewares from all plugins
  • Uses lib.ChainMiddlewares to compose them
  • Gracefully handles empty plugin lists

This is a clean migration from the old per-request interception pattern to middleware composition.


201-214: TracingMiddleware design with atomic plugin updates is well thought out.

Using atomic.Pointer[[]schemas.ObservabilityPlugin] allows thread-safe updates to observability plugins at runtime (e.g., during hot-reload) without requiring locks on the hot path. The constructor properly initializes and stores the initial plugin list.


284-299: GetObservabilityPlugins helper is clean and correct.

The type assertion pattern correctly filters plugins implementing ObservabilityPlugin interface. Early return for empty input is a good optimization.


247-277: Async trace forwarding pattern is sound, but lacks graceful shutdown coordination.

The defer with goroutine pattern appropriately ensures trace completion and async execution, using context.Background() since request context is done. However, the trace forwarding goroutines spawned here are untracked and will not be coordinated during server shutdown—traces may be lost if the server terminates while these goroutines are in flight.

For production deployments, consider tracking in-flight traces with sync.WaitGroup if guaranteed delivery is required.

transports/bifrost-http/server/server.go (1)

958-1037: HTTP middleware type migration and tracing bootstrap look consistent

The migration to schemas.BifrostHTTPMiddleware across RegisterInferenceRoutes, RegisterAPIRoutes, RegisterUIRoutes, and PrepareCommonMiddlewares, plus the new bootstrap logic that:

  • Prepends handlers.TransportInterceptorMiddleware to inference middlewares, and
  • When any schemas.ObservabilityPlugin is loaded, creates a TraceStore/Tracer, injects it into s.Client via SetTracer, and prepends the tracing middleware

is internally consistent and preserves the intended middleware ordering for inference traffic.

This neatly centralizes tracing/bootstrap concerns in the server without leaking HTTP details into core.

Also applies to: 1087-1098, 1205-1246

plugins/otel/converter.go (1)

49-70: OTEL conversion logic for traces/spans/events looks solid

The OTEL converter:

  • Normalizes IDs with hexToBytes,
  • Maps SpanKind/SpanStatus appropriately,
  • Translates span and event attributes via convertAttributesToKeyValues/anyToKeyValue, and
  • Builds ResourceSpan with reasonable resource and instrumentation scope metadata,

all in a nil-safe way for attributes and events. As long as callers never pass a nil *schemas.Trace, this should behave correctly and produce well-formed OTLP payloads.

Also applies to: 72-87, 90-107, 112-147, 148-229, 231-257, 259-269, 271-285

core/schemas/tracer.go (1)

6-47: Tracer abstraction and NoOpTracer default are well-structured

The Tracer interface cleanly captures the operations Bifrost needs (trace lifecycle, span lifecycle, attributes, LLM request/response enrichment), and NoOpTracer provides a safe default that keeps all call sites simple (no nil checks) while avoiding overhead when tracing is disabled.

The compile-time assertion and DefaultTracer() helper round out the contract cleanly.

Also applies to: 49-86

framework/plugins/dynamicplugin.go (1)

24-29: Dynamic plugin HTTP middleware wiring matches the new plugin contract

The DynamicPlugin now stores an httpTransportMiddleware of type schemas.BifrostHTTPMiddleware, exposes it via HTTPTransportMiddleware(), and loadDynamicPlugin looks up the HTTPTransportMiddleware symbol and asserts it to that function type. This matches the updated plugin interface (exporting a func HTTPTransportMiddleware(next fasthttp.RequestHandler) fasthttp.RequestHandler) and integrates cleanly with the HTTP transport middleware chain.

Dynamic plugins that don’t need HTTP interception can simply implement a pass-through middleware.

Also applies to: 36-39, 140-147

framework/tracing/tracer.go (6)

23-44: LGTM!

The trace lifecycle methods correctly delegate to the underlying store with appropriate nil checks. The comment on line 35 properly documents caller responsibility for releasing traces.


46-75: LGTM!

The StartSpan implementation correctly handles parent-child span relationships and maintains context propagation. The defensive nil checks ensure robustness when trace or span creation fails.


77-84: LGTM!

EndSpan correctly delegates to the store with defensive type checking. Passing nil for attributes is appropriate since attributes should be set via SetAttribute during the span's lifetime.


86-100: LGTM!

SetAttribute is properly defensive with nil checks at each step. The delegation pattern to the underlying trace and span is clean and correct.


102-120: LGTM!

AddEvent follows the same defensive pattern as SetAttribute. Using time.Now() for the event timestamp is appropriate.


122-162: LGTM!

The LLM attribute population methods correctly integrate with the helper functions from llmspan.go. The pattern of retrieving attribute maps and applying them to spans is clean, and all nil checks are in place.

framework/tracing/llmspan.go (6)

12-60: LGTM!

PopulateRequestAttributes provides a clean entry point with proper nil checking and comprehensive request type coverage. The delegation pattern to type-specific populators is well-structured.


62-124: LGTM!

Both response and error attribute population functions are properly nil-safe and provide comprehensive coverage. The conditional checks for optional error fields are appropriate.


126-153: LGTM!

PopulateContextAttributes correctly populates optional context metadata with appropriate conditional checks. Setting retry and fallback counts unconditionally is appropriate since zero is a meaningful value.


206-936: LGTM!

The type-specific attribute population functions (Chat, TextCompletion, Embedding, Transcription, Speech, Responses, Batch, and File operations) all follow consistent patterns with proper nil checks and conditional handling of optional fields. The implementation is comprehensive and correct, aside from the ExtraParams collision issue already noted.


1121-1237: LGTM!

The message extraction helpers are well-structured with appropriate nil checks throughout. The MessageSummary and ResponsesMessageSummary types provide clean abstractions for tracing, and the content extraction logic correctly handles both string and block-based content formats.


1243-1255: LGTM!

PopulateCostAttribute correctly handles optional pricing integration with appropriate nil checks. The integration with ModelCatalog.CalculateCostWithCacheDebug provides cost tracking when a pricing manager is available.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from b8f95ce to fb945b9 Compare December 23, 2025 10:37
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (5)
ui/components/ui/switch.tsx (1)

13-32: UI changes appear unrelated to the PR's stated objective.

This PR is described as "plugins v2 architecture" focusing on Go backend changes (HTTP middleware, plugin interfaces, transport layer). However, this file contains UI component modifications (default size and border radius changes) that seem unrelated to the plugin architecture refactor.

Consider moving these UI changes to a separate PR for better separation of concerns and clearer change tracking.

docs/plugins/writing-plugin.mdx (1)

54-59: Update go.mod example version to support HTTPTransportMiddleware

The v1.4.x+ example plugin code uses HTTPTransportMiddleware() and schemas.BifrostHTTPMiddleware (lines 66–102), but the go.mod snippet pins github.com/maximhq/bifrost/core v1.2.38. The hello-world example in the repository uses v1.2.40 with this API. Update the pinned version to at least v1.2.40 to avoid compilation errors.

transports/bifrost-http/integrations/router.go (1)

503-693: Use the bifrost context for all non-streaming client calls

handleNonStreamingRequest passes ctx (a *fasthttp.RequestCtx) to client methods, but it should use a context.Context derived from bifrostCtx, matching the pattern in handleBatchRequest (line 698) and handleFileRequest.

Add requestCtx := *bifrostCtx after the comments and use requestCtx for all client method calls:

 func (g *GenericRouter) handleNonStreamingRequest(ctx *fasthttp.RequestCtx, config RouteConfig, req interface{}, bifrostReq *schemas.BifrostRequest, bifrostCtx *context.Context) {
 	// Use the cancellable context from ConvertToBifrostContext
 	// While we can't detect client disconnects until we try to write, having a cancellable context
 	// allows providers that check ctx.Done() to cancel early if needed. This is less critical than
 	// streaming requests (where we actively detect write errors), but still provides a mechanism
 	// for providers to respect cancellation.
+	requestCtx := *bifrostCtx
 	
 	var response interface{}
 	var err error
 
 	switch {
 	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(requestCtx, bifrostReq.ListModelsRequest)

Replace ctx with requestCtx in all client method calls: ListModelsRequest, TextCompletionRequest, ChatCompletionRequest, ResponsesRequest, EmbeddingRequest, SpeechRequest, and TranscriptionRequest.

core/bifrost.go (2)

2743-2850: Streaming detection via BifrostContextKeyStreamStartTime breaks non-stream plugin spans

executeRequestWithRetries now unconditionally sets:

streamStartTime := time.Now()
*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyStreamStartTime, streamStartTime)
*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyTracer, tracer)

for all request types (streaming and non-streaming). PluginPipeline.RunPostHooks detects “streaming mode” via:

isStreaming := (*ctx).Value(schemas.BifrostContextKeyStreamStartTime) != nil

and, when isStreaming is true, it:

  • Skips per-plugin posthook spans and
  • Only accumulates timing into postHookTimings/chunkCount.

Because BifrostContextKeyStreamStartTime is now always set in the worker context, every non-stream request is treated as streaming in RunPostHooks, which:

  • Silently removes the existing per-plugin post-hook spans for all synchronous calls.
  • Accumulates timing that is never flushed for non-stream flows (no finalizer is invoked).

You likely want one of:

  • Only setting BifrostContextKeyStreamStartTime for stream requests (e.g., pass an isStreaming flag into executeRequestWithRetries and gate the WithValue calls), or
  • Changing RunPostHooks to key off a dedicated streaming flag (e.g., BifrostContextKeyDeferTraceCompletion or similar) instead of the presence of a timestamp used for TTFT.

As-is, this is a functional regression for plugin observability on non-streaming requests.

Also applies to: 3261-3323


2978-2996: Streaming PluginPipeline lifetime vs pooling can race and drop metrics

For streaming requests, requestWorker does:

pipeline := bifrost.getPluginPipeline()
postHookRunner := func(...) { return pipeline.RunPostHooks(...) }
// stores postHookSpanFinalizer capturing `pipeline` into req.Context
...
stream, bifrostError = executeRequestWithRetries(..., postHookRunner, ...)
...
if pipeline != nil {
    bifrost.releasePluginPipeline(pipeline) // calls reset and returns to pool
}

Meanwhile, providers receive postHookRunner and (by design) call it from their streaming goroutines as chunks arrive. Additionally, HTTP/transport code is expected to call the postHookSpanFinalizer closure (which also closes spans on pipeline) at the end of streaming, using the reference stored in the context.

This creates two problems:

  1. pipeline is reset and returned to the pool while streaming goroutines and the finalizer may still be using it → data races and incorrect postHookTimings/chunkCount.
  2. Because resetPluginPipeline clears postHookTimings and postHookPluginOrder, FinalizeStreamingPostHookSpans will frequently see an empty map and produce no aggregated spans, even if RunPostHooks was called many times during streaming.

You probably want to:

  • Keep the streaming PluginPipeline alive until the stream truly ends (e.g., release it from the place that closes the stream / calls the finalizer), or
  • Avoid pooling PluginPipeline for streaming at all, and only reuse it for non-streaming flows.

As implemented, the combination of pooling + streaming callbacks is unsafe and undermines the new aggregated post-hook span metrics.

Also applies to: 3370-3423

♻️ Duplicate comments (9)
ui/components/ui/switch.tsx (1)

13-13: Counterintuitive naming: "md" is smaller than "default" and is now the default.

The past review correctly identified that "md" renders smaller (h-5 w-9) than "default" (h-6 w-11), which is confusing. Setting size = "md" as the default parameter exacerbates this—users now get the smaller variant by default, and "medium" being smaller than "default" remains counterintuitive.

Consider renaming to make the size relationship clear (e.g., "sm" and "md", or "compact" and "default"), and set the default parameter to represent the actual baseline size.

examples/plugins/hello-world/main.go (1)

7-7: Hello-world example still reads a context value that HTTPTransportMiddleware never sets.

PreHook/PostHook log hello-world-plugin-transport-interceptor, but the new HTTPTransportMiddleware only prints and forwards, so that key is always nil and the data‑flow demo is broken.

Consider either:

  • Updating HTTPTransportMiddleware to extract the *schemas.BifrostContext from fasthttp.RequestCtx (e.g., via ctx.UserValue("bifrost-context")) and set the expected key, or
  • Removing the reads of "hello-world-plugin-transport-interceptor" from PreHook/PostHook if you no longer want to demonstrate that flow.

Also applies to: 19-43

framework/tracing/store.go (1)

339-367: Harden TraceStore.Stop against double calls and clean up deferred spans

Stop() currently stops the ticker and unconditionally closes s.stopCleanup:

func (s *TraceStore) Stop() {
	if s.cleanupTicker != nil {
		s.cleanupTicker.Stop()
	}
	close(s.stopCleanup)
	s.cleanupWg.Wait()
}

If Stop() is ever called more than once (or called after a zero-TTL store that never started cleanup), this will panic with “close of closed channel”. A prior review already flagged this pattern.

Separately, cleanupOldTraces only evicts entries from s.traces. Any matching entries in deferredSpans for those trace IDs will remain, which is a small but real leak over time for abandoned traces.

Proposed fix: guard Stop() with sync.Once and clear deferred spans for evicted traces
 type TraceStore struct {
@@
-	ttl           time.Duration
-	cleanupTicker *time.Ticker
-	stopCleanup   chan struct{}
-	cleanupWg     sync.WaitGroup
+	ttl           time.Duration
+	cleanupTicker *time.Ticker
+	stopCleanup   chan struct{}
+	cleanupWg     sync.WaitGroup
+	stopOnce      sync.Once
 }
@@
 func (s *TraceStore) cleanupOldTraces() {
 	cutoff := time.Now().Add(-s.ttl)
 	count := 0
 
-	s.traces.Range(func(key, value any) bool {
-		trace := value.(*schemas.Trace)
-		if trace.StartTime.Before(cutoff) {
-			if deleted, ok := s.traces.LoadAndDelete(key); ok {
-				s.ReleaseTrace(deleted.(*schemas.Trace))
-				count++
-			}
-		}
-		return true
-	})
+	s.traces.Range(func(key, value any) bool {
+		traceID := key.(string)
+		trace := value.(*schemas.Trace)
+		if trace.StartTime.Before(cutoff) {
+			if deleted, ok := s.traces.LoadAndDelete(key); ok {
+				s.ReleaseTrace(deleted.(*schemas.Trace))
+				// also clear any deferred span for this trace
+				s.deferredSpans.Delete(traceID)
+				count++
+			}
+		}
+		return true
+	})
@@
 func (s *TraceStore) Stop() {
-	if s.cleanupTicker != nil {
-		s.cleanupTicker.Stop()
-	}
-	close(s.stopCleanup)
-	s.cleanupWg.Wait()
+	s.stopOnce.Do(func() {
+		if s.cleanupTicker != nil {
+			s.cleanupTicker.Stop()
+		}
+		close(s.stopCleanup)
+		s.cleanupWg.Wait()
+	})
 }
plugins/governance/main.go (1)

158-181: Tighten virtual key handling and error messages in HTTP transport middleware

  1. Missing prefix validation for x-bf-vk (duplicate of earlier review)
    parseVirtualKey returns the x-bf-vk header as-is:

    vkHeader := ctx.Request.Header.Peek("x-bf-vk")
    if string(vkHeader) != "" {
        return bifrost.Ptr(string(vkHeader))
    }

    but both Authorization: Bearer ... and x-api-key paths enforce VirtualKeyPrefix. This inconsistency allows arbitrary values via x-bf-vk that would be rejected on other headers.

  2. Misleading log messages for JSON (un)marshaling (duplicate of earlier review)
    In HTTPTransportMiddleware, you log:

    err = sonic.Unmarshal(ctx.Request.Body(), &payload)
    if err != nil {
        p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
    }
    ...
    body, err := sonic.Marshal(payload)
    if err != nil {
        p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
    }

    The first message should say “unmarshal”, and both messages should describe what the code is actually doing (parsing request JSON / rebuilding body after load-balancing), not “check for virtual key”.

  3. Body presence check is fragile
    ctx.Request.Body() returns a []byte; comparing it to nil might still treat an empty body as “present” on some paths. That leads to unnecessary JSON parsing attempts and noisy error logs for routes that legitimately have no body.

    Safer is to check len(body) == 0 and skip parsing in that case.

Proposed fixes
 func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
 	var virtualKeyValue string
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
-	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
-	}
+	if vk := string(vkHeader); vk != "" && strings.HasPrefix(strings.ToLower(vk), VirtualKeyPrefix) {
+		return bifrost.Ptr(vk)
+	}
@@
 func (p *GovernancePlugin) HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
@@
-			if ctx.Request.Body() == nil {
+			bodyBytes := ctx.Request.Body()
+			if len(bodyBytes) == 0 {
 				next(ctx)
 				return
 			}
 			var payload map[string]any
-			err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+			err = sonic.Unmarshal(bodyBytes, &payload)
 			if err != nil {
-				p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+				p.logger.Error("failed to unmarshal request body for governance HTTP middleware: %v", err)
 				next(ctx)
 				return
 			}
@@
 			payload, err = p.loadBalanceProvider(payload, virtualKey)
 			if err != nil {
-				p.logger.Error("failed to load balance provider: %v", err)
+				p.logger.Error("failed to load balance provider in governance HTTP middleware: %v", err)
 				next(ctx)
 				return
 			}
 			body, err := sonic.Marshal(payload)
 			if err != nil {
-				p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+				p.logger.Error("failed to marshal updated request body in governance HTTP middleware: %v", err)
 				next(ctx)
 				return
 			}

Also applies to: 183-234

core/bifrost.go (1)

70-75: SetTracer is unsafe for concurrent use and allows nil (risking panics)

Bifrost.tracer is read from hot paths (workers, fallback spans, plugin pipeline) without synchronization, while SetTracer mutates it directly and is public. In addition:

  • SetTracer allows tracer to be set to nil, but all call sites assume a non-nil tracer (bifrost.tracer.StartSpan(...) etc.), so a runtime call to SetTracer(nil) would lead to panics.

Given this is a global cross-cutting dependency:

  • Either treat SetTracer as init-only (document it clearly and possibly guard with a once/“started” flag), or
  • Make tracer concurrency-safe (e.g., atomic.Value with Load/Store) and normalize nil to schemas.DefaultTracer() so it’s never nil at use sites.

This repeats an earlier concern and is still present in the current diff.

Also applies to: 246-250

framework/tracing/propagation.go (1)

24-36: ExtractParentID should return ParentID, not TraceID

The function is documented to extract the parent ID from the traceparent header but currently returns ctx.TraceID. Per W3C format, the parent/span ID is the third field and should map to ctx.ParentID.

Proposed fix
 func ExtractParentID(header *fasthttp.RequestHeader) string {
@@
 	ctx := ParseTraceparent(traceParent)
 	if ctx == nil {
 		return ""
 	}
-	return ctx.TraceID
+	return ctx.ParentID
 }
core/schemas/trace.go (1)

40-49: Trace.Reset, Span.End, and Span.Reset mutate state without locking

Trace.AddSpan / Trace.GetSpan and Span.SetAttribute / Span.AddEvent are mutex-protected, but:

  • Trace.Reset clears fields and slices with no lock.
  • Span.End sets EndTime, Status, StatusMsg without locking.
  • Span.Reset clears all fields and the Events slice without locking.

Given these types are shared through TraceStore and may be read/updated concurrently, calling these methods while another goroutine accesses the same trace/span can produce data races.

Either:

  • Guard these methods with the same mutex (mu.Lock / defer mu.Unlock()), or
  • Very clearly document and enforce that they may only be called when the trace/span is no longer reachable by any other goroutine (e.g., after removal from maps and before returning to pools).

Example for Span.End:

func (s *Span) End(status SpanStatus, statusMsg string) {
-    s.EndTime = time.Now()
-    s.Status = status
-    s.StatusMsg = statusMsg
+    s.mu.Lock()
+    defer s.mu.Unlock()
+    s.EndTime = time.Now()
+    s.Status = status
+    s.StatusMsg = statusMsg
}

Same pattern applies to Trace.Reset and Span.Reset.

Also applies to: 84-104

framework/tracing/tracer.go (1)

18-21: Comments still refer to “StoreTracer” instead of Tracer

There are several references to “StoreTracer” in comments:

  • Constructor comment: // NewTracer creates a new StoreTracer...
  • Type comment for spanHandle.
  • Compile-time assertion comment: // Ensure StoreTracer implements schemas.Tracer...

The actual exported type is Tracer, so these comments should be updated for consistency and to avoid confusion.

Example fixes:

-// NewTracer creates a new StoreTracer wrapping the given TraceStore.
+// NewTracer creates a new Tracer wrapping the given TraceStore.
@@
-// spanHandle is the concrete implementation of schemas.SpanHandle for StoreTracer.
+// spanHandle is the concrete implementation of schemas.SpanHandle for Tracer.
@@
-// Ensure StoreTracer implements schemas.Tracer at compile time
+// Ensure Tracer implements schemas.Tracer at compile time

Also applies to: 46-51, 447-448

framework/tracing/llmspan.go (1)

190-193: ExtraParams collision issue remains unaddressed.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing, which can overwrite standard attributes (e.g., if a user sets a key like "gen_ai.request.model").

This issue was previously flagged and affects multiple locations in this file (lines 190-193, 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029).

🔎 Suggested approach: namespace ExtraParams consistently
 	// ExtraParams
 	for k, v := range req.Params.ExtraParams {
-		attrs[k] = fmt.Sprintf("%v", v)
+		attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 	}

Apply this pattern consistently across all 14 occurrences in the file.

🧹 Nitpick comments (6)
core/providers/utils/utils.go (1)

880-925: Deferred LLM span completion for streaming looks correct, but check error/ordering edge cases.

The new tracing hooks in ProcessAndSendResponse/ProcessAndSendBifrostError plus completeDeferredSpan are defensively implemented and should safely no-op when tracing isn’t configured. Chunk accumulation + GetAccumulatedChunks gives you a solid basis to populate LLM response attributes, TTFT, and total chunks before ending the deferred span.

Two edge cases are worth double‑checking:

  1. Ordering relative to post hooks (final chunk).
    completeDeferredSpan is called before postHookRunner on the final chunk, while it uses BifrostContextKeyPostHookSpanFinalizer to synthesize aggregated post‑hook spans. If that finalizer is intended to account for post‑hook execution on the last chunk as well, you may want to move the completeDeferredSpan call after postHookRunner for the final chunk (or adjust the finalizer’s expectations).

  2. Scanner / transport errors via ProcessAndSendError.
    Only ProcessAndSendResponse and ProcessAndSendBifrostError trigger completeDeferredSpan when BifrostContextKeyStreamEndIndicator is true. For stream failures that go through ProcessAndSendError (e.g., low‑level read/parse errors), there currently isn’t a path here to close the deferred span, which could leave traces open until some external timeout.

Also applies to: 932-969, 1412-1501

core/schemas/plugin.go (1)

4-8: Plugin API evolution is coherent, but docs still mention TransportInterceptor.

Adding BifrostHTTPMiddleware and switching the Plugin interface to HTTPTransportMiddleware() BifrostHTTPMiddleware, plus the new ObservabilityPlugin interface, aligns with the rest of the middleware and tracing changes.

One minor doc mismatch remains: the “Execution order” comment block still refers to TransportInterceptor as step 1. It should be updated to describe HTTPTransportMiddleware as the first stage in the pipeline to avoid confusing plugin authors migrating to the new API.

Also applies to: 36-39, 71-80, 109-137

transports/bifrost-http/server/server.go (1)

1231-1246: Consider managing the tracing store’s lifecycle on server shutdown

The Bootstrap logic:

  • Prepends handlers.TransportInterceptorMiddleware to inferenceMiddlewares.
  • When any ObservabilityPlugin is present, allocates a TraceStore with a 60-minute TTL, builds a tracing.Tracer, calls s.Client.SetTracer(tracer), and prepends a tracing middleware.

This is a good integration of HTTP middleware, client tracing, and observability plugins. One improvement would be to keep a reference to the TraceStore (or a closer on the tracer) on BifrostHTTPServer and stop it during shutdown, so the cleanup goroutine and its ticker are explicitly torn down rather than relying on process exit.

plugins/otel/converter.go (1)

49-70: Consider stricter validation in hexToBytes for trace/span IDs

hexToBytes strips non-hex chars, pads/truncates, and ignores hex.DecodeString errors. For trace IDs/span IDs this can silently turn malformed inputs into “valid” OTEL IDs, which hurts debuggability and cross-system correlation.

If feasible, consider:

  • Failing fast (returning nil and skipping span export) when DecodeString errors, or
  • Validating length/charset at call sites (e.g., before convertSpanToOTELSpan) and logging when IDs are bad.

This is an optional hardening improvement; current behavior is functional but very permissive.

framework/tracing/helpers.go (1)

22-82: Defensively guard against nil *TraceStore in helpers

GetTrace, AddSpan, AddChildSpan, EndSpan, SetSpanAttribute, and AddSpanEvent all dereference store without checking for nil. If callers ever pass a nil *TraceStore (e.g., during partial initialization or tests), these will panic.

Consider early returns like:

if store == nil {
    return // or nil for the getters
}

in these helpers to make them safer to use across the codebase.

framework/tracing/llmspan.go (1)

280-280: Consider preserving structured data instead of string conversion.

Maps and structured data are being converted to string representations using fmt.Sprintf("%v", ...) at lines 280 (LogitBias), 613 (Metadata), and 705 (Metadata).

This flattens the structure and reduces queryability in tracing backends that support nested attributes. Consider using structured attribute values if the tracing backend supports them, or use JSON serialization for better structure preservation.

💡 Alternative serialization approach

For better structure preservation, consider JSON serialization:

-		attrs[schemas.AttrLogitBias] = fmt.Sprintf("%v", req.Params.LogitBias)
+		if data, err := json.Marshal(req.Params.LogitBias); err == nil {
+			attrs[schemas.AttrLogitBias] = string(data)
+		}

Note: This requires importing encoding/json.

Also applies to: 613-613, 705-705

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b8f95ce and fb945b9.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (65)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/go.mod
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (2)
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
🚧 Files skipped from review as they are similar to previous changes (22)
  • transports/bifrost-http/handlers/websocket.go
  • transports/go.mod
  • transports/bifrost-http/handlers/plugins.go
  • docs/plugins/getting-started.mdx
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/bifrost_test.go
  • plugins/maxim/main.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/providers.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • plugins/governance/go.mod
  • framework/plugins/dynamicplugin_test.go
  • ui/components/sidebar.tsx
  • core/mcp.go
  • framework/plugins/dynamicplugin.go
  • ui/components/ui/sheet.tsx
  • transports/bifrost-http/handlers/config.go
  • examples/plugins/hello-world/go.mod
  • plugins/logging/main.go
  • docs/plugins/migration-guide.mdx
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • framework/streaming/types.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/logging.go
  • framework/streaming/responses.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/ui.go
  • framework/streaming/transcription.go
  • docs/docs.json
  • transports/bifrost-http/server/server.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/telemetry/main.go
  • ui/components/ui/switch.tsx
  • docs/plugins/writing-plugin.mdx
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • plugins/governance/main.go
  • framework/streaming/chat.go
  • core/schemas/context.go
  • framework/streaming/accumulator.go
  • core/providers/utils/utils.go
  • framework/tracing/propagation.go
  • plugins/otel/main.go
  • core/schemas/bifrost.go
  • core/go.mod
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/mcpServer.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/integrations.go
  • examples/plugins/hello-world/main.go
  • core/schemas/tracer.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/session.go
  • plugins/jsonparser/main.go
  • core/bifrost.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
🧠 Learnings (4)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • framework/streaming/types.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/logging.go
  • framework/streaming/responses.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/ui.go
  • framework/streaming/transcription.go
  • transports/bifrost-http/server/server.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/telemetry/main.go
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • plugins/governance/main.go
  • framework/streaming/chat.go
  • core/schemas/context.go
  • framework/streaming/accumulator.go
  • core/providers/utils/utils.go
  • framework/tracing/propagation.go
  • plugins/otel/main.go
  • core/schemas/bifrost.go
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/mcpServer.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/integrations.go
  • examples/plugins/hello-world/main.go
  • core/schemas/tracer.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/session.go
  • plugins/jsonparser/main.go
  • core/bifrost.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/session.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (32)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/server/server.go (4)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
transports/bifrost-http/handlers/middlewares.go (2)
  • TransportInterceptorMiddleware (50-70)
  • NewTracingMiddleware (207-214)
framework/tracing/store.go (1)
  • NewTraceStore (38-65)
framework/tracing/tracer.go (1)
  • NewTracer (19-21)
framework/tracing/store.go (5)
core/schemas/tracer.go (1)
  • Tracer (13-73)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
core/schemas/bifrost.go (1)
  • BifrostResponse (335-355)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
transports/bifrost-http/handlers/middlewares.go (6)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
core/schemas/tracer.go (1)
  • Tracer (13-73)
framework/tracing/propagation.go (1)
  • ExtractParentID (26-36)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/chat.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/schemas/context.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (147-147)
plugins/otel/main.go (3)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • PluginShortCircuit (12-16)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/trace.go (1)
  • Trace (10-19)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (13-73)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (163-163)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/audio.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/tracer.go (4)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/schemas/bifrost.go (3)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
framework/tracing/tracer.go (1)
  • Tracer (14-16)
transports/bifrost-http/integrations/router.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (9)
  • ListModelsRequest (92-92)
  • TextCompletionRequest (93-93)
  • ChatCompletionRequest (95-95)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/inference.go (5)
  • ChatRequest (172-176)
  • ResponsesRequest (266-270)
  • EmbeddingRequest (273-277)
  • SpeechRequest (279-283)
  • TranscriptionRequest (285-289)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/bifrost.go (3)
core/schemas/tracer.go (3)
  • Tracer (13-73)
  • DefaultTracer (124-126)
  • SpanHandle (8-8)
core/schemas/bifrost.go (5)
  • BifrostRequest (173-193)
  • BifrostContextKeyStreamStartTime (145-145)
  • BifrostContextKeyTracer (146-146)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (24-35)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
framework/tracing/llmspan.go (5)
core/schemas/bifrost.go (18)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • TextCompletionRequest (93-93)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • SpeechRequest (100-100)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (100)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (216-216)
  • AttrErrorType (217-217)
  • AttrErrorCode (218-218)
  • AttrVirtualKeyID (228-228)
  • AttrVirtualKeyName (229-229)
  • AttrSelectedKeyID (230-230)
  • AttrSelectedKeyName (231-231)
  • AttrTeamID (232-232)
  • AttrTeamName (233-233)
  • AttrCustomerID (234-234)
  • AttrCustomerName (235-235)
  • AttrNumberOfRetries (236-236)
  • AttrFallbackIndex (237-237)
  • AttrMaxTokens (163-163)
  • AttrTemperature (164-164)
  • AttrTopP (165-165)
  • AttrStopSequences (166-166)
  • AttrPresencePenalty (167-167)
  • AttrFrequencyPenalty (168-168)
  • AttrParallelToolCall (169-169)
  • AttrRequestUser (170-170)
  • AttrMessageCount (188-188)
  • AttrInputMessages (222-222)
  • AttrResponseID (191-191)
  • AttrResponseModel (192-192)
  • AttrObject (197-197)
  • AttrSystemFprint (194-194)
  • AttrCreated (196-196)
  • AttrServiceTier (195-195)
  • AttrOutputMessages (225-225)
  • AttrFinishReason (193-193)
  • AttrTotalTokens (210-210)
  • AttrN (175-175)
  • AttrInputText (221-221)
  • AttrDimensions (178-178)
  • AttrEncodingFormat (179-179)
  • AttrInputEmbedding (224-224)
  • AttrLanguage (180-180)
  • AttrPrompt (181-181)
  • AttrResponseFormat (182-182)
  • AttrFormat (183-183)
  • AttrInputTokens (211-211)
  • AttrOutputTokens (212-212)
  • AttrInputSpeech (223-223)
  • AttrRespInclude (255-255)
  • AttrRespMaxOutputTokens (256-256)
  • AttrRespMaxToolCalls (257-257)
  • AttrRespMetadata (258-258)
  • AttrRespPreviousRespID (259-259)
  • AttrRespPromptCacheKey (260-260)
  • AttrRespReasoningText (261-261)
  • AttrRespReasoningEffort (262-262)
  • AttrRespReasoningGenSum (263-263)
  • AttrRespSafetyIdentifier (264-264)
  • AttrRespStore (265-265)
  • AttrRespTemperature (266-266)
  • AttrRespTextVerbosity (267-267)
  • AttrRespTextFormatType (268-268)
  • AttrRespTopLogProbs (269-269)
  • AttrRespTopP (270-270)
  • AttrRespToolChoiceType (271-271)
  • AttrRespToolChoiceName (272-272)
  • AttrRespTruncation (273-273)
  • AttrRespTools (274-274)
  • AttrBatchInputFileID (281-281)
  • AttrBatchEndpoint (280-280)
  • AttrBatchCompletionWin (284-284)
  • AttrBatchRequestsCount (287-287)
  • AttrBatchMetadata (291-291)
  • AttrBatchLimit (292-292)
  • AttrBatchAfter (293-293)
  • AttrBatchBeforeID (294-294)
  • AttrBatchAfterID (295-295)
  • AttrBatchPageToken (296-296)
  • AttrBatchPageSize (297-297)
  • AttrBatchID (277-277)
  • AttrBatchStatus (278-278)
  • AttrBatchObject (279-279)
  • AttrBatchCreatedAt (285-285)
  • AttrBatchExpiresAt (286-286)
  • AttrBatchOutputFileID (282-282)
  • AttrBatchErrorFileID (283-283)
  • AttrBatchCountTotal (298-298)
  • AttrBatchCountCompleted (299-299)
  • AttrBatchCountFailed (300-300)
  • AttrBatchDataCount (288-288)
  • AttrBatchHasMore (290-290)
  • AttrBatchFirstID (301-301)
  • AttrBatchLastID (302-302)
  • AttrBatchInProgressAt (303-303)
  • AttrBatchFinalizingAt (304-304)
  • AttrBatchCompletedAt (305-305)
  • AttrBatchFailedAt (306-306)
  • AttrBatchExpiredAt (307-307)
  • AttrBatchCancellingAt (308-308)
  • AttrBatchCancelledAt (309-309)
  • AttrBatchNextCursor (310-310)
  • AttrUsageCost (213-213)
core/schemas/chatcompletions.go (1)
  • BifrostChatRequest (12-19)
core/schemas/embedding.go (1)
  • BifrostEmbeddingRequest (9-16)
core/schemas/responses.go (1)
  • BifrostResponsesRequest (32-39)
framework/tracing/tracer.go (5)
framework/tracing/store.go (1)
  • TraceStore (24-35)
core/schemas/tracer.go (2)
  • Tracer (13-73)
  • SpanHandle (8-8)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (4)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from fb945b9 to 1cecccb Compare December 24, 2025 07:24
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (12)
ui/components/ui/switch.tsx (1)

13-13: Note: Size default change already flagged in previous review.

The change from size = "default" (implicit previous default) to size = "md" has already been identified as a breaking change in the past review comment. Existing Switch components without an explicit size prop will render smaller (h-6 w-11 → h-5 w-9).

examples/plugins/hello-world/main.go (1)

19-26: HTTPTransportMiddleware implementation looks correct but context value propagation is broken.

The middleware correctly implements the schemas.BifrostHTTPMiddleware signature with proper delegation to next(ctx). However, PreHook (line 29) and PostHook (lines 38-39) still read "hello-world-plugin-transport-interceptor" from context, which this middleware never sets—those reads will always return nil.

Either set the context value in the middleware to demonstrate data flow, or remove the stale context reads from the hooks.

plugins/governance/main.go (2)

30-36: Validate x-bf-vk header consistently with other virtual key sources

parseVirtualKey returns the raw x-bf-vk header without checking it starts with VirtualKeyPrefix ("sk-bf-"), while both Authorization: Bearer and x-api-key paths enforce that prefix. This inconsistency can allow malformed or unintended tokens via x-bf-vk to bypass the prefix check.

Align x-bf-vk handling with the other branches, e.g.:

Proposed adjustment
 func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
-	var virtualKeyValue string
-	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
-	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
-	}
+	var virtualKeyValue string
+
+	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
+	if len(vkHeader) > 0 {
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
+			return bifrost.Ptr(vkValue)
+		}
+	}

This keeps all header sources enforcing the same VK prefix semantics.

Also applies to: 158-181


183-231: Fix HTTP middleware log message and empty-body handling

Two small correctness/UX issues in HTTPTransportMiddleware:

  • Line 214 logs "failed to marshal" while calling sonic.Unmarshal. This should say "unmarshal" to reflect the actual operation.
  • The empty-body check only guards on ctx.Request.Body() == nil. In fasthttp, an empty body is often a zero-length slice, not nil, so you’ll still try to unmarshal and log an error for valid but empty requests.

Consider:

Proposed tweak
-	if ctx.Request.Body() == nil {
+	if body := ctx.Request.Body(); body == nil || len(body) == 0 {
 		next(ctx)
 		return
 	}
 	var payload map[string]any
-	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
 	if err != nil {
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+		p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)
 		next(ctx)
 		return
 	}
core/bifrost.go (1)

55-75: Clarify SetTracer’s concurrency model or make it race-free

Bifrost.tracer is read on hot paths (handleRequest, handleStreamRequest, requestWorker, executeRequestWithRetries, plugin pipeline) without synchronization, while SetTracer mutates it directly:

type Bifrost struct {
    ...
    tracer schemas.Tracer
}

func (bifrost *Bifrost) SetTracer(tracer schemas.Tracer) {
    bifrost.tracer = tracer
}

In the current server flow SetTracer is only called during bootstrap (before requests start), which is safe. But as a public method it invites runtime reconfiguration, which would introduce a data race.

Consider either:

  • Treating SetTracer as init-only: document it as such and avoid calling it after workers start, or
  • Making it concurrency-safe (e.g., store the tracer in an atomic.Value / atomic.Pointer or a small RW-mutex-protected field, and load via that everywhere).
In Go, is writing to a struct field from one goroutine while other goroutines read it without synchronization considered a data race?

Also applies to: 120-139, 247-250

framework/tracing/store.go (1)

23-35: Guard TraceStore.Stop() against multiple invocations (double close panic)

Stop() unconditionally closes s.stopCleanup. If Stop() is called more than once (e.g., from tests or multiple shutdown paths), closing an already-closed channel will panic. This was previously flagged and the pattern is unchanged.

Consider adding a sync.Once field to TraceStore and wrapping the shutdown logic so it runs only once:

Proposed fix
 type TraceStore struct {
 	traces        sync.Map // map[traceID]*schemas.Trace - thread-safe concurrent access
 	deferredSpans sync.Map // map[traceID]*DeferredSpanInfo - deferred spans for streaming requests
 	tracePool     sync.Pool // Reuse Trace objects to reduce allocations
 	spanPool      sync.Pool // Reuse Span objects to reduce allocations
 	logger        schemas.Logger

 	ttl           time.Duration
 	cleanupTicker *time.Ticker
 	stopCleanup   chan struct{}
 	cleanupWg     sync.WaitGroup
+	stopOnce      sync.Once
 }

 // Stop stops the cleanup goroutine and releases resources
 func (s *TraceStore) Stop() {
-	if s.cleanupTicker != nil {
-		s.cleanupTicker.Stop()
-	}
-	close(s.stopCleanup)
-	s.cleanupWg.Wait()
+	s.stopOnce.Do(func() {
+		if s.cleanupTicker != nil {
+			s.cleanupTicker.Stop()
+		}
+		close(s.stopCleanup)
+		s.cleanupWg.Wait()
+	})
 }
In Go, what happens if you call close() on a channel more than once?

Also applies to: 360-367

framework/tracing/propagation.go (1)

24-36: ExtractParentID should return ParentID, not TraceID

The function is documented as extracting the parent trace/span ID from the traceparent header, but after parsing it returns ctx.TraceID:

ctx := ParseTraceparent(traceParent)
...
return ctx.TraceID

Per the W3C format (version-traceid-parentid-traceflags), the parent/span ID is the third field and should be surfaced via ctx.ParentID. Returning the trace ID here will break proper parent-child linking for incoming traces.

Recommend:

 func ExtractParentID(header *fasthttp.RequestHeader) string {
   traceParent := string(header.Peek(TraceParentHeader))
   if traceParent == "" {
     return ""
   }
   ctx := ParseTraceparent(traceParent)
   if ctx == nil {
     return ""
   }
-  return ctx.TraceID
+  return ctx.ParentID
 }
In the W3C Trace Context `traceparent` header, which field represents the parent/span ID?
framework/tracing/llmspan.go (1)

190-193: ExtraParams keys written directly may cause attribute key collisions.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing. If a user sets a key like "gen_ai.request.model" in ExtraParams, it would overwrite the standard attribute.

This pattern repeats throughout the file (lines 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029).

🔎 Suggested approach: namespace ExtraParams
 // ExtraParams
 for k, v := range req.Params.ExtraParams {
-    attrs[k] = fmt.Sprintf("%v", v)
+    attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 }
core/schemas/trace.go (3)

40-49: Trace.Reset() lacks mutex protection.

This method modifies all fields including Spans without holding the mutex, creating a potential data race if Reset is called while another goroutine is accessing the trace.


84-89: Span.End() lacks mutex protection.

This method modifies EndTime, Status, and StatusMsg without holding the mutex, which can cause data races if called concurrently with reads or writes to these fields.


91-104: Span.Reset() lacks mutex protection.

Similar to Trace.Reset(), this method modifies fields without holding the mutex. If reused from a pool while another goroutine still holds a reference, a data race could occur.

framework/tracing/tracer.go (1)

393-398: Missing sort for deterministic output in buildCompleteResponseFromChunks.

The choiceIndices (line 395-398) and tcIndices (line 434-437) slices are built from map iteration but are not sorted before use. This results in non-deterministic ordering of choices and tool calls in the reconstructed response.

🔎 Proposed fix
+import "sort"

 // Build final choices from accumulated data
 // Sort choice indices for deterministic output
 choiceIndices := make([]int, 0, len(choiceMap))
 for idx := range choiceMap {
     choiceIndices = append(choiceIndices, idx)
 }
+sort.Ints(choiceIndices)

 for _, idx := range choiceIndices {
     ...
         // Sort tool calls by index
         tcIndices := make([]int, 0, len(accum.toolCalls))
         for tcIdx := range accum.toolCalls {
             tcIndices = append(tcIndices, tcIdx)
         }
+        sort.Ints(tcIndices)

Also applies to: 433-437

🧹 Nitpick comments (9)
transports/bifrost-http/handlers/middlewares.go (1)

49-70: Plugin middleware collection is correct; consider pre-allocating the slice.

The implementation correctly:

  • Collects HTTPTransportMiddleware() from all plugins
  • Skips nil middlewares
  • Chains them via lib.ChainMiddlewares

For a minor efficiency gain on hot paths, consider pre-allocating the slice:

🔎 Optional optimization
-pluginsMiddlewareChain := []schemas.BifrostHTTPMiddleware{}
+pluginsMiddlewareChain := make([]schemas.BifrostHTTPMiddleware, 0, len(plugins))
framework/streaming/responses.go (1)

742-756: LGTM! TTFT calculation correctly implemented.

The Time to First Token calculation is properly implemented with defensive checks for zero timestamps. The logic correctly computes TTFT in milliseconds and propagates it through AccumulatedData.

Optional: Consider extracting TTFT calculation to a helper function.

The same TTFT calculation logic is duplicated across multiple streaming files (responses.go, chat.go, transcription.go). Consider extracting this into a shared helper function to reduce duplication and improve maintainability.

🔎 Proposed helper function
+// calculateTTFT calculates Time to First Token in milliseconds
+func calculateTTFT(startTimestamp, firstChunkTimestamp time.Time) int64 {
+	if startTimestamp.IsZero() || firstChunkTimestamp.IsZero() {
+		return 0
+	}
+	return firstChunkTimestamp.Sub(startTimestamp).Nanoseconds() / 1e6
+}

 // Calculate Time to First Token (TTFT) in milliseconds
-var ttft int64
-if !accumulator.StartTimestamp.IsZero() && !accumulator.FirstChunkTimestamp.IsZero() {
-	ttft = accumulator.FirstChunkTimestamp.Sub(accumulator.StartTimestamp).Nanoseconds() / 1e6
-}
+ttft := calculateTTFT(accumulator.StartTimestamp, accumulator.FirstChunkTimestamp)
core/bifrost_test.go (1)

71-72: LGTM! Test calls correctly updated for new tracing integration.

All test call sites have been consistently updated to include the tracer parameter (schemas.DefaultTracer()) and request parameter (nil), aligning with the new signature of executeRequestWithRetries.

Optional: Consider clarifying the nil parameter.

The trailing nil parameter passed to executeRequestWithRetries could be clearer with a comment or named constant (e.g., var nilRequest *schemas.BifrostRequest = nil) to improve test readability.

Also applies to: 106-107, 141-142, 205-206, 279-280, 509-510

plugins/semanticcache/main.go (1)

338-341: Semantic cache transport middleware + CacheType gating look sane, with a small edge-case

Returning nil from HTTPTransportMiddleware is fine if the transport chain skips nils. The new CacheTypeKey logic correctly narrows to direct-only or semantic-only caching when set.

Be aware that if a new CacheType value is ever introduced and written into context, both performDirectSearch and performSemanticSearch will be false, effectively disabling cache lookup. If you’d rather fall back to “both enabled” on unknown values, you could keep the defaults and only flip the one that should be disabled.

Please confirm that callers only ever set CacheTypeKey to CacheTypeDirect or CacheTypeSemantic at this point; otherwise you may want a default/fallback branch.

Also applies to: 381-390

core/providers/utils/utils.go (1)

881-900: Streaming tracing integration is coherent; consider covering scanner-error path too

The new tracing flow makes sense:

  • Each streaming chunk is accumulated via Tracer.AddStreamingChunk.
  • Final chunks (as marked by BifrostContextKeyStreamEndIndicator) call completeDeferredSpan, which:
    • Looks up the deferred LLM span by traceID.
    • Uses GetAccumulatedChunks to drive PopulateLLMResponseAttributes.
    • Sets TTFT and total chunk attributes.
    • Finalizes post-hook spans via BifrostContextKeyPostHookSpanFinalizer and ends the LLM span with OK/Error.
    • Clears the deferred span.

ProcessAndSendResponse and ProcessAndSendBifrostError are both wired into this, so normal and provider-error streaming terminations are handled.

One gap you may want to close: ProcessAndSendError (the low-level stream read/scanner error path) doesn’t currently invoke completeDeferredSpan, so a streaming request that fails before a BifrostResponse/BifrostError is produced could leave the deferred LLM span open. If that function is reachable after the LLM span has been created, calling completeDeferredSpan(&ctx, nil, bifrostError) there as well would make streaming span completion fully symmetric.

Please double-check where ProcessAndSendError is used; if it’s part of the same LLM call lifecycle, consider wiring it into completeDeferredSpan to avoid dangling spans on scanner failures.

Also applies to: 939-945, 1412-1501

docs/plugins/writing-plugin.mdx (1)

53-59: Plugin v2 example is correct; consider aligning the documented core version with v1.4.x+

The v1.4.x+ tab’s hello-world plugin:

  • Exposes HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware,
  • Uses the standard next fasthttp.RequestHandler pattern, and
  • Keeps Init, GetName, PreHook, PostHook, and Cleanup signatures consistent with the loader,

which matches the updated Plugin interface and the example code in examples/plugins/hello-world.

One documentation tweak: Step 1’s go.mod still pins github.com/maximhq/bifrost/core v1.2.38, while the v1.4.x+ code relies on the newer HTTP middleware surface. It might be clearer either to:

  • Update that snippet to a v1.4.x-compatible core version, or
  • Annotate that snippet as v1.3.x-only and add a separate v1.4.x+ go.mod example (or simply say “match the Bifrost version you’re running”).

Also applies to: 65-127, 213-241, 429-444

core/bifrost.go (1)

3270-3436: Streaming post‑hook timing aggregation and spans are well-structured

The new PluginPipeline streaming timing machinery looks coherent:

  • RunPostHooks switches to timing accumulation when BifrostContextKeyStreamStartTime is present, avoiding per-chunk span spam.
  • accumulatePluginTiming maintains per-plugin totals and call counts plus error counts, recording order of first occurrence.
  • FinalizeStreamingPostHookSpans builds a nested span hierarchy (plugin.<name>.posthook) in that order, sets aggregated metrics (invocations, avg/total duration, error count), and closes spans in reverse order like a call stack.

This design gives a clear, low-cardinality representation of streaming plugin cost while keeping allocation and span volume low.

framework/tracing/helpers.go (1)

10-83: Tracing helpers are straightforward; consider optional nil-store guard

The context-based helpers (GetTraceID, GetTrace, AddSpan, AddChildSpan, EndSpan, SetSpanAttribute, AddSpanEvent) are all defensive w.r.t. missing trace IDs and missing spans, which is good.

If you expect tests or optional wiring to sometimes pass a nil *TraceStore, a small extra guard in GetTrace/AddSpan/EndSpan (early-return on nil store) would make them fully panic-proof, but that’s optional given current usage.

plugins/otel/converter.go (1)

49-70: OTEL conversion path looks good; minor type-coverage nits only

The trace/span → OTEL conversion is generally solid:

  • hexToBytes cleans and normalizes IDs to fixed byte lengths before assigning TraceId/SpanId/ParentSpanId.
  • convertTraceToResourceSpan and convertSpanToOTELSpan correctly fill IDs, timestamps, attributes, status, events, and parent span linkage.
  • anyToKeyValue and convertAttributesToKeyValues cover the common scalar/slice/map cases and gracefully skip empty values.

If you want to tighten things further, consider:

  • Adding explicit handling for []bool in anyToKeyValue so boolean slices don’t fall back to string formatting.
  • Optionally logging or asserting when hex.DecodeString in hexToBytes fails (currently the error is ignored, though the input is sanitized first).

These are minor polish items; the core conversion logic is sound.

Also applies to: 72-107, 133-229, 271-285

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fb945b9 and 1cecccb.

⛔ Files ignored due to path filters (2)
  • core/go.sum is excluded by !**/*.sum
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
📒 Files selected for processing (66)
  • core/bifrost.go
  • core/bifrost_test.go
  • core/go.mod
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/main.go
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/maxim/main.go
  • plugins/mocker/main.go
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/semanticcache/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/go.mod
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (2)
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (1)
  • docs/plugins/migration-guide.mdx
🚧 Files skipped from review as they are similar to previous changes (24)
  • core/go.mod
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/logging.go
  • framework/plugins/dynamicplugin_test.go
  • core/schemas/context.go
  • core/mcp.go
  • framework/streaming/types.go
  • plugins/maxim/main.go
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/ui.go
  • transports/go.mod
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/middleware.go
  • core/schemas/bifrost.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/config.go
  • ui/components/sidebar.tsx
  • docs/docs.json
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/health.go
  • framework/plugins/dynamicplugin.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/logging/utils.go
  • transports/bifrost-http/handlers/governance.go
  • framework/streaming/responses.go
  • plugins/semanticcache/main.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/integrations.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/session.go
  • framework/streaming/chat.go
  • framework/streaming/transcription.go
  • core/bifrost_test.go
  • framework/tracing/store.go
  • plugins/governance/main.go
  • docs/plugins/writing-plugin.mdx
  • core/providers/utils/utils.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/cache.go
  • framework/tracing/propagation.go
  • plugins/telemetry/main.go
  • plugins/otel/main.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/plugins.go
  • plugins/otel/converter.go
  • ui/components/ui/sheet.tsx
  • examples/plugins/hello-world/go.mod
  • framework/tracing/helpers.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/mcpServer.go
  • core/schemas/trace.go
  • ui/components/ui/switch.tsx
  • docs/plugins/getting-started.mdx
  • core/schemas/tracer.go
  • framework/tracing/tracer.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/tracing/llmspan.go
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • plugins/logging/utils.go
  • transports/bifrost-http/handlers/governance.go
  • framework/streaming/responses.go
  • plugins/semanticcache/main.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/integrations.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/session.go
  • framework/streaming/chat.go
  • framework/streaming/transcription.go
  • core/bifrost_test.go
  • framework/tracing/store.go
  • plugins/governance/main.go
  • core/providers/utils/utils.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/cache.go
  • framework/tracing/propagation.go
  • plugins/telemetry/main.go
  • plugins/otel/main.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/plugins.go
  • plugins/otel/converter.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/mcpServer.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • framework/tracing/tracer.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/tracing/llmspan.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/mcpServer.go
  • transports/bifrost-http/handlers/middlewares.go
📚 Learning: 2025-12-24T04:36:57.698Z
Learnt from: danpiths
Repo: maximhq/bifrost PR: 1169
File: transports/bifrost-http/handlers/governance.go:1708-1878
Timestamp: 2025-12-24T04:36:57.698Z
Learning: In governance update handlers (e.g., updateModelConfig, updateProviderGovernance), design updates to support clearing individual fields by sending null/empty values (e.g., {"rate_limit": {"token_max_limit": null}} clears token_max_limit). Follow this pattern for partial updates so users can remove specific governance settings without deleting the whole entity. Ensure budget updates follow the same approach using direct field assignment. Review input validation, JSON decoding (e.g., pointers vs values in Go structs), and API documentation to reflect nullable fields and expected behavior.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (27)
plugins/logging/utils.go (3)
core/schemas/tracer.go (1)
  • StreamAccumulatorResult (15-33)
framework/streaming/types.go (9)
  • ProcessedStreamResponse (118-126)
  • StreamType (10-10)
  • StreamTypeText (13-13)
  • StreamTypeChat (14-14)
  • StreamTypeResponses (17-17)
  • StreamTypeAudio (15-15)
  • StreamTypeTranscription (16-16)
  • StreamResponseType (20-20)
  • AccumulatedData (28-48)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (649-656)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/chat.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/bifrost_test.go (1)
core/schemas/tracer.go (1)
  • DefaultTracer (174-176)
framework/tracing/store.go (1)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-26)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/providers/utils/utils.go (4)
core/schemas/bifrost.go (6)
  • BifrostResponse (335-355)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyStreamEndIndicator (131-131)
  • BifrostError (474-483)
  • BifrostContextKeyPostHookSpanFinalizer (149-149)
core/schemas/tracer.go (1)
  • Tracer (38-112)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (2)
  • AttrTimeToFirstToken (198-198)
  • AttrTotalChunks (199-199)
transports/bifrost-http/integrations/router.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (9)
  • ListModelsRequest (92-92)
  • TextCompletionRequest (93-93)
  • ChatCompletionRequest (95-95)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/inference.go (5)
  • ChatRequest (172-176)
  • ResponsesRequest (266-270)
  • EmbeddingRequest (273-277)
  • SpeechRequest (279-283)
  • TranscriptionRequest (285-289)
transports/bifrost-http/server/server.go (4)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
transports/bifrost-http/handlers/middlewares.go (2)
  • TransportInterceptorMiddleware (50-70)
  • NewTracingMiddleware (207-214)
framework/tracing/store.go (1)
  • NewTraceStore (38-65)
framework/tracing/tracer.go (1)
  • NewTracer (24-29)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (2)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/trace.go (1)
  • Trace (10-19)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (15)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (24-35)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcpServer.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (163-163)
framework/tracing/tracer.go (5)
framework/tracing/store.go (1)
  • TraceStore (24-35)
core/schemas/tracer.go (3)
  • Tracer (38-112)
  • SpanHandle (11-11)
  • StreamAccumulatorResult (15-33)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (6)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
  • BifrostContextKeyRequestID (124-124)
  • BifrostContextKeyStreamEndIndicator (131-131)
framework/streaming/types.go (1)
  • StreamResponseTypeFinal (24-24)
framework/tracing/llmspan.go (6)
core/schemas/bifrost.go (24)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • ChatCompletionRequest (95-95)
  • ChatCompletionStreamRequest (96-96)
  • TextCompletionRequest (93-93)
  • TextCompletionStreamRequest (94-94)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • TranscriptionStreamRequest (103-103)
  • SpeechRequest (100-100)
  • SpeechStreamRequest (101-101)
  • ResponsesRequest (97-97)
  • ResponsesStreamRequest (98-98)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (5)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (216-216)
  • AttrErrorType (217-217)
  • AttrErrorCode (218-218)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • BifrostChatResponse (27-42)
core/schemas/responses.go (2)
  • BifrostResponsesRequest (32-39)
  • BifrostResponsesResponse (45-85)
core/schemas/batch.go (10)
  • BifrostBatchCreateRequest (65-83)
  • BifrostBatchListRequest (118-133)
  • BifrostBatchRetrieveRequest (150-159)
  • BifrostBatchCancelRequest (205-214)
  • BifrostBatchResultsRequest (234-246)
  • BifrostBatchCreateResponse (91-115)
  • BifrostBatchListResponse (136-147)
  • BifrostBatchRetrieveResponse (167-202)
  • BifrostBatchCancelResponse (222-231)
  • BifrostBatchResultsResponse (285-294)
core/schemas/files.go (10)
  • BifrostFileUploadRequest (53-67)
  • BifrostFileListRequest (109-128)
  • BifrostFileRetrieveRequest (146-159)
  • BifrostFileDeleteRequest (186-198)
  • BifrostFileContentRequest (215-227)
  • BifrostFileUploadResponse (90-106)
  • BifrostFileListResponse (136-143)
  • BifrostFileRetrieveResponse (167-183)
  • BifrostFileDeleteResponse (206-212)
  • BifrostFileContentResponse (235-241)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 35152fe to 18b4142 Compare December 24, 2025 13:30
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

♻️ Duplicate comments (10)
transports/bifrost-http/integrations/router.go (1)

502-693: [Duplicate] Non-streaming requests still using incorrect context.

This issue was previously flagged: non-streaming calls at lines 514, 534, 557, 580, 603, 626, and 660 pass ctx (*fasthttp.RequestCtx) instead of the enriched Bifrost context (*bifrostCtx). This breaks tracing, plugin context propagation, and may cause compilation errors if the client expects context.Context.

Please refer to the previous review comment for the detailed fix.

framework/changelog.md (1)

1-25: Breaking change well-documented; tracing mention could be expanded.

The breaking change from TransportInterceptor to HTTPTransportMiddleware is thoroughly documented with code examples. The tracing framework is mentioned on line 1, but per the past review comment, consider adding a separate section with more details about the tracing APIs (Trace/Span/Tracer types, TraceStore, W3C propagation) for plugin authors who want to adopt tracing.

docs/plugins/migration-guide.mdx (1)

247-250: Version reference issue persists.

The go get github.com/maximhq/bifrost/[email protected] command references a tag that may not exist yet. This will cause the documented command to fail for users following the guide.

Consider one of:

  1. Create the v1.4.0 tag before merging
  2. Update to reference an existing version or use @latest
  3. Use a placeholder like @vX.Y.Z with a note to check the latest version
core/providers/utils/utils.go (1)

1416-1420: Context should be passed by value, not by pointer.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. The context.Context type is designed to be passed by value and is safe for concurrent use. Passing it by pointer adds unnecessary indirection and can cause confusion.

🔎 Proposed fix
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == nil {
 		return
 	}
 
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)

Update call sites accordingly:

-			completeDeferredSpan(&ctx, response, nil)
+			completeDeferredSpan(ctx, response, nil)
transports/changelog.md (1)

16-23: Version reference inconsistency with core changelog.

The migration summary states v1.3.x (removed) and v1.4.x+ (new), but the core changelog indicates the new API is available in core v1.3.0+. This inconsistency could confuse developers trying to understand which versions to target.

Please align the version references across all changelogs to ensure consistent migration guidance.

framework/plugins/dynamicplugin.go (1)

140-147: Fix HTTPTransportMiddleware symbol type assertion (factory func vs middleware func)

The dynamic plugin loader assumes HTTPTransportMiddleware is exported as a schemas.BifrostHTTPMiddleware, but the example plugin exports:

func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware { ... }

So plugin.Lookup("HTTPTransportMiddleware") returns a func() schemas.BifrostHTTPMiddleware, not the middleware itself; the current type assertion will always fail and dynamic plugins will never load.

You should assert to the factory function type, then call it to obtain the middleware. Also update the error message to match the actual expected type.

Proposed fix for HTTPTransportMiddleware lookup
 	// Looking up for HTTPTransportMiddleware method
 	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
 	if err != nil {
 		return nil, err
 	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
-	}
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
+	}
+
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()
+	if dp.httpTransportMiddleware == nil {
+		return nil, fmt.Errorf("HTTPTransportMiddleware factory returned nil middleware")
+	}
transports/bifrost-http/handlers/middlewares.go (1)

191-307: *TracingMiddleware must pass a context.Context to StartSpan, not fasthttp.RequestCtx

TracingMiddleware.Middleware currently does:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

But StartSpan expects a context.Context, while ctx here is *fasthttp.RequestCtx. This will not compile and also prevents you from attaching trace ID into the span context properly.

You should construct a proper context.Context (and inject the trace ID into it) before calling StartSpan, then continue to use the returned spanCtx for downstream work.

Proposed fix for StartSpan usage in TracingMiddleware
 func (m *TracingMiddleware) Middleware() schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
@@
-			// Extract parent trace ID from W3C headers (if present)
-			parentID := tracing.ExtractParentID(&ctx.Request.Header)
-			// Create trace in store - only ID returned (trace data stays in store)
-			traceID := m.tracer.Load().CreateTrace(parentID)
-			// Only trace ID goes into context (lightweight, no bloat)
-			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
+			// Extract parent trace ID from W3C headers (if present)
+			parentID := tracing.ExtractParentID(&ctx.Request.Header)
+			// Create trace in store - only ID returned (trace data stays in store)
+			traceID := m.tracer.Load().CreateTrace(parentID)
+			// Only trace ID goes into context/user values (lightweight, no bloat)
+			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
+
+			// Build a base context carrying the trace ID for the tracer
+			baseCtx := context.WithValue(context.Background(), schemas.BifrostContextKeyTraceID, traceID)
@@
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Create root span for the HTTP request
+			spanCtx, rootSpan := m.tracer.Load().StartSpan(baseCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
 			if rootSpan != nil {
 				m.tracer.Load().SetAttribute(rootSpan, "http.method", string(ctx.Method()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.url", string(ctx.RequestURI()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.user_agent", string(ctx.Request.Header.UserAgent()))
 				// Set root span ID in context for child span creation
 				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
 					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
 				}
 			}

This keeps trace ID propagation via ctx.UserValue while satisfying the tracer’s context.Context requirements.

core/bifrost.go (1)

70-71: Tracer initialization and accessors mostly solid; guard against SetTracer(nil) panics

  • Init now initializes a tracer from config.Tracer or falls back to schemas.DefaultTracer(), and stores it in bifrost.tracer (an atomic.Value), so there is always a tracer by default.
  • getPluginPipeline correctly injects the current tracer into PluginPipeline.
  • Shutdown defensively calls Stop() on any stored tracer.

However, SetTracer allows callers to store any schemas.Tracer value, including nil, and getTracer() unconditionally does:

return bifrost.tracer.Load().(schemas.Tracer)

If SetTracer(nil) is ever called, the next getTracer() will panic. Given SetTracer is public, it’s easy to misuse in the future.

Consider either:

  • Rejecting nil in SetTracer (log and ignore, or replace with schemas.DefaultTracer()), and/or
  • Making getTracer() defensive by checking the loaded value and falling back to schemas.DefaultTracer() when it’s nil or of the wrong type.
Example defensive adjustment
 func (bifrost *Bifrost) SetTracer(tracer schemas.Tracer) {
-	bifrost.tracer.Store(tracer)
+	if tracer == nil {
+		// Optional: log and fall back to default no-op tracer
+		tracer = schemas.DefaultTracer()
+	}
+	bifrost.tracer.Store(tracer)
 }
 
 func (bifrost *Bifrost) getTracer() schemas.Tracer {
-	return bifrost.tracer.Load().(schemas.Tracer)
+	if v := bifrost.tracer.Load(); v != nil {
+		if t, ok := v.(schemas.Tracer); ok && t != nil {
+			return t
+		}
+	}
+	return schemas.DefaultTracer()
 }

Also applies to: 78-100, 120-127, 139-140, 247-255

framework/tracing/llmspan.go (1)

191-193: Consider namespacing ExtraParams to prevent attribute key collisions.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing throughout this file. If a user sets a key that matches a standard attribute name (e.g., "gen_ai.request.model"), it would overwrite the standard attribute, potentially causing observability issues.

🔎 Suggested approach

Namespace all ExtraParams keys with a consistent prefix like "gen_ai.extra." or "user.extra." to prevent collisions:

 	// ExtraParams
 	for k, v := range req.Params.ExtraParams {
-		attrs[k] = fmt.Sprintf("%v", v)
+		attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 	}

Apply this pattern consistently across all ExtraParams handling locations in the file.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029

framework/tracing/tracer.go (1)

394-441: Add missing sort.Ints() calls for deterministic output.

The comments state "Sort choice indices for deterministic output" (line 394) and "Sort tool calls by index" (line 433), but the actual sort.Ints() calls are missing. Without sorting, map iteration order is nondeterministic in Go, causing inconsistent ordering of choices and tool calls in reconstructed responses.

🔎 Proposed fix
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)

 	for _, idx := range choiceIndices {
 		accum := choiceMap[idx]
 
 		// Build message
 		msg := &schemas.ChatMessage{
 			Role: accum.role,
 		}
 
 		// Set content
 		if accum.content != "" {
 			msg.Content = &schemas.ChatMessageContent{
 				ContentStr: &accum.content,
 			}
 		}
 
 		// Build assistant message fields
 		if accum.refusal != "" || accum.reasoning != "" || len(accum.reasoningDetails) > 0 ||
 			accum.audio != nil || len(accum.toolCalls) > 0 {
 			msg.ChatAssistantMessage = &schemas.ChatAssistantMessage{}
 
 			if accum.refusal != "" {
 				msg.ChatAssistantMessage.Refusal = &accum.refusal
 			}
 			if accum.reasoning != "" {
 				msg.ChatAssistantMessage.Reasoning = &accum.reasoning
 			}
 			if len(accum.reasoningDetails) > 0 {
 				msg.ChatAssistantMessage.ReasoningDetails = accum.reasoningDetails
 			}
 			if accum.audio != nil {
 				msg.ChatAssistantMessage.Audio = accum.audio
 			}
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
 					toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
 				}
 				msg.ChatAssistantMessage.ToolCalls = toolCalls
 			}
 		}

Add the import:

 import (
 	"context"
+	"sort"
 	"time"
 
 	"github.com/maximhq/bifrost/core/schemas"

Note: Past review comments indicate this was addressed in commit 1cecccb, but the fix appears to be missing in the current code.

🧹 Nitpick comments (5)
plugins/jsonparser/main.go (1)

95-102: Minor: Empty line in doc comment.

Line 95 has an empty doc comment line (//) which is unnecessary.

🔎 Proposed fix
 // PreHook is not used for this plugin as we only process responses
 // Parameters:
 //   - ctx: The Bifrost context
 //   - req: The Bifrost request
-//
 // Returns:
 //   - *schemas.BifrostRequest: The processed request
 //   - *schemas.PluginShortCircuit: The plugin short circuit if the request is not allowed
 //   - error: Any error that occurred during processing
transports/changelog.md (1)

16-23: Add language specifier to fenced code block.

Static analysis indicates the code block should have a language specified for proper syntax highlighting.

🔎 Proposed fix
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)

   // v1.4.x+ (new)
   HTTPTransportMiddleware() BifrostHTTPMiddleware
   // where BifrostHTTPMiddleware = func(next fasthttp.RequestHandler) fasthttp.RequestHandler
   ```
transports/bifrost-http/server/server.go (1)

1040-1043: UI route registration signature matches new middleware type

RegisterUIRoutes now takes ...schemas.BifrostHTTPMiddleware and forwards them to the UI handler. Currently Bootstrap calls s.RegisterUIRoutes() with no middlewares, which is fine; if you later want metrics/tracing on UI endpoints, you can pass commonMiddlewares here as well.

transports/bifrost-http/handlers/middlewares.go (1)

49-68: Plugin HTTP transport middleware chaining is correct; caching chain could be a future optimization

TransportInterceptorMiddleware correctly:

  • Pulls loaded plugins from config.GetLoadedPlugins().
  • Collects non‑nil plugin.HTTPTransportMiddleware() values.
  • Chains them via lib.ChainMiddlewares preserving order.

This is functionally sound; if performance ever becomes a concern, you could cache the composed chain per config.Plugins snapshot instead of rebuilding on every request.

framework/tracing/helpers.go (1)

1-83: Context-based tracing helpers are straightforward; consider guarding nil TraceStore

The helpers (GetTraceID, GetTrace, AddSpan, AddChildSpan, EndSpan, SetSpanAttribute, AddSpanEvent) correctly:

  • Derive traceID from schemas.BifrostContextKeyTraceID in the context.
  • No‑op when no trace ID is present.
  • Delegate span operations to TraceStore / schemas.Trace.

As long as callers always pass a non‑nil *TraceStore, this is safe. If there’s any chance of misuse, you could defensively return early when store == nil to avoid panics.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 03b6361 and 35152fe.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (89)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (1)
  • plugins/logging/version
🚧 Files skipped from review as they are similar to previous changes (35)
  • plugins/otel/version
  • .github/workflows/scripts/release-bifrost-http.sh
  • transports/bifrost-http/handlers/health.go
  • .github/workflows/release-pipeline.yml
  • plugins/maxim/version
  • transports/bifrost-http/handlers/logging.go
  • docs/docs.json
  • framework/streaming/audio.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/types.go
  • core/version
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • plugins/telemetry/version
  • examples/plugins/hello-world/main.go
  • plugins/governance/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • plugins/otel/changelog.md
  • ui/components/ui/switch.tsx
  • plugins/semanticcache/version
  • transports/bifrost-http/handlers/middlewares_test.go
  • plugins/mocker/main.go
  • plugins/maxim/changelog.md
  • framework/version
  • docs/plugins/getting-started.mdx
  • core/schemas/bifrost.go
  • plugins/maxim/main.go
  • plugins/logging/utils.go
  • transports/version
  • plugins/semanticcache/changelog.md
  • plugins/mocker/version
  • ui/components/sidebar.tsx
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/config.go
  • plugins/governance/main.go
  • plugins/jsonparser/version
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/logging/changelog.md
  • core/schemas/plugin.go
  • plugins/jsonparser/changelog.md
  • transports/bifrost-http/handlers/governance.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/integrations/router.go
  • framework/streaming/chat.go
  • transports/bifrost-http/handlers/mcp.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/websocket.go
  • framework/streaming/transcription.go
  • framework/tracing/store.go
  • core/schemas/context.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/session.go
  • core/changelog.md
  • plugins/otel/main.go
  • transports/changelog.md
  • framework/streaming/accumulator.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/server/server.go
  • docs/plugins/writing-plugin.mdx
  • framework/tracing/propagation.go
  • framework/changelog.md
  • transports/bifrost-http/handlers/ui.go
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/middlewares.go
  • ui/components/ui/sheet.tsx
  • transports/bifrost-http/lib/middleware.go
  • framework/streaming/responses.go
  • plugins/telemetry/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • docs/plugins/migration-guide.mdx
  • plugins/governance/changelog.md
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • examples/plugins/hello-world/go.mod
  • plugins/logging/main.go
  • core/schemas/tracer.go
  • plugins/otel/converter.go
  • core/bifrost.go
  • plugins/jsonparser/main.go
  • plugins/semanticcache/main.go
  • core/providers/utils/utils.go
  • plugins/mocker/changelog.md
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/handlers/plugins.go
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/mcpserver.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/governance.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/integrations/router.go
  • framework/streaming/chat.go
  • transports/bifrost-http/handlers/mcp.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/websocket.go
  • framework/streaming/transcription.go
  • framework/tracing/store.go
  • core/schemas/context.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/session.go
  • plugins/otel/main.go
  • framework/streaming/accumulator.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/server/server.go
  • framework/tracing/propagation.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/logging/main.go
  • core/schemas/tracer.go
  • plugins/otel/converter.go
  • core/bifrost.go
  • plugins/jsonparser/main.go
  • plugins/semanticcache/main.go
  • core/providers/utils/utils.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/handlers/plugins.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/plugins.go
📚 Learning: 2025-12-24T04:36:57.698Z
Learnt from: danpiths
Repo: maximhq/bifrost PR: 1169
File: transports/bifrost-http/handlers/governance.go:1708-1878
Timestamp: 2025-12-24T04:36:57.698Z
Learning: In governance update handlers (e.g., updateModelConfig, updateProviderGovernance), design updates to support clearing individual fields by sending null/empty values (e.g., {"rate_limit": {"token_max_limit": null}} clears token_max_limit). Follow this pattern for partial updates so users can remove specific governance settings without deleting the whole entity. Ensure budget updates follow the same approach using direct field assignment. Review input validation, JSON decoding (e.g., pointers vs values in Go structs), and API documentation to reflect nullable fields and expected behavior.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (31)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
framework/streaming/chat.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/bifrost_test.go (1)
core/schemas/tracer.go (1)
  • DefaultTracer (181-183)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
framework/tracing/store.go (4)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/bifrost.go (1)
  • BifrostResponse (335-355)
core/schemas/trace.go (5)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
core/schemas/context.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (147-147)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (2)
core/schemas/plugin.go (1)
  • ObservabilityPlugin (123-137)
core/schemas/trace.go (1)
  • Trace (10-19)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (6)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (3)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/tracer.go (7)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/bifrost.go (3)
  • ModelProvider (33-33)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
core/schemas/context.go (1)
  • BifrostContext (32-42)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/providers/utils/utils.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
framework/tracing/tracer.go (6)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • SpanHandle (11-11)
  • StreamAccumulatorResult (15-33)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (4)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
framework/tracing/llmspan.go (1)
  • PopulateResponseAttributes (64-106)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (12)
core/providers/utils/utils.go (1)

1416-1420: Pass context by value, not by pointer.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. context.Context is designed to be passed by value and is safe for concurrent use. Passing it by pointer is an anti-pattern that can lead to subtle bugs.

The fix requires changing the signature to accept context.Context and updating all call sites (lines 897, 942) to pass ctx instead of &ctx.

docs/plugins/migration-guide.mdx (1)

247-250: Version reference may be invalid at merge time.

Line 249 references @v1.4.0, but this tag may not exist when users follow this guide. Consider using a placeholder like @latest or updating after the release is tagged.

transports/changelog.md (1)

16-23: Version reference inconsistency persists.

The migration summary shows v1.3.x (removed) and v1.4.x+ (new), but ensure this aligns with the actual versioning across all changelogs. The core changelog should be checked for consistency.

plugins/governance/main.go (2)

286-292: Fix error message: operation is unmarshal, not marshal.

Line 289 logs "failed to marshal request body" but the operation on line 287 is sonic.Unmarshal. The error message should say "unmarshal".

🔎 Proposed fix
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+		p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)

233-256: x-bf-vk header missing VirtualKeyPrefix validation.

The x-bf-vk header (lines 236-238) is returned without validating that it starts with VirtualKeyPrefix, while the Authorization Bearer token (lines 241-246) and x-api-key (lines 251-253) both validate the prefix. This inconsistency could allow invalid virtual keys to pass through when provided via the x-bf-vk header.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
+			return bifrost.Ptr(vkValue)
+		}
 	}
transports/bifrost-http/integrations/router.go (1)

501-507: Non‑streaming Bifrost calls must use bifrostCtx, not *fasthttp.RequestCtx

handleNonStreamingRequest still passes ctx (a *fasthttp.RequestCtx) into Bifrost client methods instead of the cancellable/enriched *bifrostCtx created by ConvertToBifrostContext. This likely won’t compile against the client signatures and also drops all tracing and plugin context values stored on bifrostCtx.

Proposed fix: use the converted context for all non‑streaming Bifrost client calls
 func (g *GenericRouter) handleNonStreamingRequest(ctx *fasthttp.RequestCtx, config RouteConfig, req interface{}, bifrostReq *schemas.BifrostRequest, bifrostCtx *context.Context) {
-	
+	// Use the cancellable/enriched context created in createHandler
+	requestCtx := *bifrostCtx
@@
 	switch {
 	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(requestCtx, bifrostReq.ListModelsRequest)
@@
 	case bifrostReq.TextCompletionRequest != nil:
-		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(requestCtx, bifrostReq.TextCompletionRequest)
@@
 	case bifrostReq.ChatRequest != nil:
-		chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+		chatResponse, bifrostErr := g.client.ChatCompletionRequest(requestCtx, bifrostReq.ChatRequest)
@@
 	case bifrostReq.ResponsesRequest != nil:
-		responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+		responsesResponse, bifrostErr := g.client.ResponsesRequest(requestCtx, bifrostReq.ResponsesRequest)
@@
 	case bifrostReq.EmbeddingRequest != nil:
-		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(requestCtx, bifrostReq.EmbeddingRequest)
@@
 	case bifrostReq.SpeechRequest != nil:
-		speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+		speechResponse, bifrostErr := g.client.SpeechRequest(requestCtx, bifrostReq.SpeechRequest)
@@
 	case bifrostReq.TranscriptionRequest != nil:
-		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(requestCtx, bifrostReq.TranscriptionRequest)

Also applies to: 512-683

framework/plugins/dynamicplugin.go (1)

24-29: Fix HTTPTransportMiddleware symbol type: plugin exports a factory function, not a middleware

Dynamic plugins export:

func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware { ... }

but loadDynamicPlugin currently asserts the symbol directly to schemas.BifrostHTTPMiddleware. That will always fail at runtime; the symbol is a func() schemas.BifrostHTTPMiddleware.

You need to assert to the factory function and then call it to obtain the middleware, similar to how GetName is handled.

Proposed fix for `HTTPTransportMiddleware` lookup
	// Looking up for HTTPTransportMiddleware method
	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
	if err != nil {
		return nil, err
	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
-	}
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
+	}
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()

Also applies to: 36-39, 140-147

transports/bifrost-http/handlers/middlewares.go (1)

191-279: TracingMiddleware must call StartSpan with a context.Context carrying the trace ID, not *fasthttp.RequestCtx

StartSpan is defined to take a context.Context, and the tracing helpers expect the trace ID to live in that context (via BifrostContextKeyTraceID). Currently:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

where ctx is *fasthttp.RequestCtx. This both fails the type contract and prevents StartSpan from seeing the trace ID you just generated.

Proposed fix: build a proper context with the trace ID before calling `StartSpan`
			// Create trace in store - only ID returned (trace data stays in store)
			traceID := m.tracer.Load().CreateTrace(parentID)
			// Only trace ID goes into context (lightweight, no bloat)
			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
@@
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Create a standard context carrying the trace ID for the tracer
+			spanCtxInput := context.WithValue(context.Background(), schemas.BifrostContextKeyTraceID, traceID)
+			// Create root span for the HTTP request
+			spanCtx, rootSpan := m.tracer.Load().StartSpan(spanCtxInput, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

The existing code that extracts spanID from spanCtx and stores it in ctx via SetUserValue can stay as‑is.

framework/tracing/llmspan.go (1)

190-193: Namespace ExtraParams attributes to avoid collisions with standard keys

Across multiple helpers, ExtraParams are written directly into the attrs map:

for k, v := range req.Params.ExtraParams {
    attrs[k] = fmt.Sprintf("%v", v)
}

The same pattern appears for batch/file ExtraParams, too. This means user-defined keys can overwrite core attributes like gen_ai.request.model or other reserved names, which was previously flagged.

Consider:

  • Prefixing user keys with a dedicated namespace (e.g. gen_ai.extra.), and
  • Optionally guarding against overwriting existing keys.
Example of namespacing ExtraParams
- for k, v := range req.Params.ExtraParams {
-     attrs[k] = fmt.Sprintf("%v", v)
- }
+ const extraPrefix = "gen_ai.extra."
+ for k, v := range req.Params.ExtraParams {
+     key := extraPrefix + k
+     if _, exists := attrs[key]; !exists {
+         attrs[key] = fmt.Sprintf("%v", v)
+     }
+ }

Apply the same pattern to other ExtraParams blocks (chat/text/embeddings/responses/batch/file) for consistency.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 769-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029

core/bifrost.go (1)

70-71: Guard SetTracer(nil) / invalid values to avoid getTracer panics

getTracer() blindly does bifrost.tracer.Load().(schemas.Tracer), while SetTracer accepts any value (including nil). A SetTracer(nil) (or storing a non‑schemas.Tracer by mistake) will cause a runtime panic the next time getTracer() runs, even though you default to DefaultTracer() in Init.

Since SetTracer is public and can be called at runtime, this is a real foot‑gun.

Consider hardening both the setter and getter:

  • In SetTracer, reject or normalize nil (e.g., log and fall back to schemas.DefaultTracer()).
  • In getTracer, handle nil/invalid values defensively and fall back to schemas.DefaultTracer() instead of panicking.
Proposed defensive implementation
 func (bifrost *Bifrost) SetTracer(tracer schemas.Tracer) {
-	bifrost.tracer.Store(tracer)
+	if tracer == nil {
+		// Optional: log once if you want visibility
+		if bifrost.logger != nil {
+			bifrost.logger.Warn("SetTracer called with nil tracer, falling back to DefaultTracer")
+		}
+		tracer = schemas.DefaultTracer()
+	}
+	bifrost.tracer.Store(tracer)
 }
 
 // getTracer returns the tracer from atomic storage with type assertion.
 func (bifrost *Bifrost) getTracer() schemas.Tracer {
-	return bifrost.tracer.Load().(schemas.Tracer)
+	if v := bifrost.tracer.Load(); v != nil {
+		if t, ok := v.(schemas.Tracer); ok && t != nil {
+			return t
+		}
+		if bifrost.logger != nil {
+			bifrost.logger.Warn("invalid or nil tracer in atomic storage, using DefaultTracer")
+		}
+	}
+	return schemas.DefaultTracer()
 }

Also applies to: 122-127, 139-140, 247-255

framework/tracing/tracer.go (2)

54-60: Fix stale comment referencing “StoreTracer”

The spanHandle comment still refers to “StoreTracer”, but the concrete type is just Tracer. Updating the wording will avoid confusion for readers and tools.


233-458: Make buildCompleteResponseFromChunks deterministic for choices and tool calls

You build choiceMap and toolCalls as maps, then iterate over their keys without sorting, even though the comments mention “Sort … for deterministic output”. Go map iteration order is intentionally random, so:

  • Choice ordering across result.ChatResponse.Choices will be nondeterministic.
  • Tool call ordering within a message will also be nondeterministic.

For trace replay, testing, and observability, this non‑determinism is undesirable.

Recommend:

  • Sort choiceIndices before the loop.
  • Sort tcIndices before building toolCalls.

You’ll also need to import sort.

Proposed deterministic ordering
 import (
 	"context"
 	"time"
 
 	"github.com/maximhq/bifrost/core/schemas"
 	"github.com/maximhq/bifrost/framework/modelcatalog"
 	"github.com/maximhq/bifrost/framework/streaming"
+	"sort"
 )
@@
 	// Build final choices from accumulated data
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)
@@
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
 					toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
 				}
🧹 Nitpick comments (3)
plugins/maxim/changelog.md (1)

1-2: Consider consolidating version upgrade entries.

Having two separate chore entries for version upgrades is slightly redundant. Consider consolidating into a single entry showing the final versions (core 1.3.0, framework 1.2.0).

🔎 Suggested consolidation
-- chore: upgraded versions of core to 1.2.41 and framework to 1.1.51
-- chore: upgraded versions of core to 1.3.0 and framework to 1.2.0
+- chore: upgraded versions of core to 1.3.0 and framework to 1.2.0
framework/tracing/propagation.go (1)

118-121: Consider handling unknown versions per W3C spec.

The W3C Trace Context spec recommends that implementations SHOULD try to extract trace-id, parent-id, and the sampled bit from higher-than-supported versions rather than rejecting them outright. Currently, any version other than "00" returns nil.

🔎 Possible enhancement
 	// Validate version (only 00 is currently supported)
-	if version != "00" {
+	if len(version) != 2 || !isHex(version) {
 		return nil
 	}
+	// For unknown versions > 00, still attempt to parse trace-id and parent-id

This would allow forward compatibility with future W3C versions while maintaining strict validation of the hex format.

framework/tracing/tracer.go (1)

17-20: Consider stopping the streaming accumulator in Tracer.Stop()

NewTracer embeds a streaming.Accumulator, which (per its constructor) prewarms pools and starts cleanup/background work. Tracer.Stop() currently only stops the underlying TraceStore, so any goroutines or timers owned by the accumulator may continue running after shutdown.

If streaming.Accumulator exposes a Stop/Close or similar, it would be cleaner to invoke it here:

func (t *Tracer) Stop() {
	if t.store != nil {
		t.store.Stop()
	}
	if t.accumulator != nil {
		t.accumulator.Stop() // or equivalent, if available
	}
}

This keeps tracing shutdown self‑contained and avoids potential resource leaks.

Also applies to: 24-29, 539-545

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 35152fe and 18b4142.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (89)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (3)
  • framework/configstore/tables/mcp.go
  • core/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (3)
  • framework/version
  • plugins/maxim/version
  • core/version
🚧 Files skipped from review as they are similar to previous changes (38)
  • core/schemas/context.go
  • plugins/governance/version
  • ui/components/ui/sheet.tsx
  • framework/streaming/types.go
  • docs/plugins/getting-started.mdx
  • transports/bifrost-http/handlers/integrations.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • plugins/telemetry/version
  • plugins/governance/go.mod
  • plugins/semanticcache/version
  • transports/bifrost-http/handlers/governance.go
  • plugins/telemetry/main.go
  • framework/streaming/chat.go
  • transports/bifrost-http/handlers/providers.go
  • .github/workflows/scripts/release-bifrost-http.sh
  • framework/streaming/responses.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/handlers/ui.go
  • transports/version
  • transports/bifrost-http/handlers/health.go
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/session.go
  • plugins/otel/main.go
  • plugins/logging/version
  • transports/bifrost-http/handlers/plugins.go
  • framework/changelog.md
  • docs/docs.json
  • transports/bifrost-http/handlers/cache.go
  • plugins/otel/changelog.md
  • core/schemas/trace.go
  • framework/streaming/audio.go
  • core/bifrost_test.go
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • ui/components/ui/switch.tsx
  • plugins/jsonparser/version
  • transports/bifrost-http/handlers/mcp.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/logging.go
  • plugins/mocker/main.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/config.go
  • docs/plugins/writing-plugin.mdx
  • plugins/mocker/version
  • framework/plugins/dynamicplugin.go
  • plugins/logging/utils.go
  • docs/plugins/migration-guide.mdx
  • plugins/governance/main.go
  • core/schemas/plugin.go
  • core/changelog.md
  • transports/changelog.md
  • transports/bifrost-http/lib/middleware.go
  • plugins/logging/main.go
  • ui/components/sidebar.tsx
  • transports/bifrost-http/integrations/router.go
  • core/providers/utils/utils.go
  • framework/streaming/accumulator.go
  • plugins/telemetry/changelog.md
  • plugins/semanticcache/changelog.md
  • plugins/maxim/changelog.md
  • framework/streaming/transcription.go
  • framework/tracing/propagation.go
  • plugins/maxim/main.go
  • framework/tracing/helpers.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/mocker/changelog.md
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • core/bifrost.go
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • core/schemas/bifrost.go
  • core/schemas/tracer.go
  • plugins/jsonparser/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • framework/tracing/tracer.go
  • framework/tracing/llmspan.go
  • plugins/otel/version
  • transports/bifrost-http/server/server.go
  • plugins/governance/changelog.md
  • plugins/logging/changelog.md
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/logging.go
  • plugins/mocker/main.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/handlers/config.go
  • framework/plugins/dynamicplugin.go
  • plugins/logging/utils.go
  • plugins/governance/main.go
  • core/schemas/plugin.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/logging/main.go
  • transports/bifrost-http/integrations/router.go
  • core/providers/utils/utils.go
  • framework/streaming/accumulator.go
  • framework/streaming/transcription.go
  • framework/tracing/propagation.go
  • plugins/maxim/main.go
  • framework/tracing/helpers.go
  • framework/tracing/store.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • core/bifrost.go
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/middlewares.go
  • core/schemas/bifrost.go
  • core/schemas/tracer.go
  • transports/bifrost-http/handlers/inference.go
  • framework/tracing/tracer.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
🧬 Code graph analysis (25)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (4)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/bifrost.go (2)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
plugins/logging/utils.go (2)
core/schemas/bifrost.go (6)
  • RequestType (89-89)
  • TextCompletionStreamRequest (94-94)
  • ChatCompletionStreamRequest (96-96)
  • ResponsesStreamRequest (98-98)
  • SpeechStreamRequest (101-101)
  • TranscriptionStreamRequest (103-103)
framework/streaming/types.go (11)
  • ProcessedStreamResponse (118-126)
  • StreamType (10-10)
  • StreamTypeText (13-13)
  • StreamTypeChat (14-14)
  • StreamTypeResponses (17-17)
  • StreamTypeAudio (15-15)
  • StreamTypeTranscription (16-16)
  • StreamResponseType (20-20)
  • StreamResponseTypeFinal (24-24)
  • StreamResponseTypeDelta (23-23)
  • AccumulatedData (28-48)
core/schemas/plugin.go (3)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
ui/lib/types/plugins.ts (1)
  • Plugin (12-19)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/integrations/router.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (9)
  • ListModelsRequest (92-92)
  • TextCompletionRequest (93-93)
  • ChatCompletionRequest (95-95)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/inference.go (5)
  • ChatRequest (172-176)
  • ResponsesRequest (266-270)
  • EmbeddingRequest (273-277)
  • SpeechRequest (279-283)
  • TranscriptionRequest (285-289)
core/providers/utils/utils.go (5)
core/schemas/provider.go (1)
  • PostHookRunner (312-312)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (4)
  • AttrTimeToFirstToken (198-198)
  • AttrTotalChunks (199-199)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
framework/tracing/store.go (1)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
core/bifrost.go (6)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • DefaultTracer (181-183)
  • SpanHandle (11-11)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (10)
  • SpanKindFallback (129-129)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
  • SpanKind (115-115)
  • SpanKindRetry (127-127)
  • SpanKindLLMCall (121-121)
  • SpanKindInternal (139-139)
  • SpanKindPlugin (123-123)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/context.go (1)
  • NewBifrostContextWithTimeout (70-73)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/tracer.go (8)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/bifrost.go (2)
  • ModelProvider (33-33)
  • BifrostResponse (335-355)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
framework/tracing/tracer.go (10)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (2)
  • Tracer (38-116)
  • SpanHandle (11-11)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (6)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
  • BifrostContextKeyRequestID (124-124)
  • BifrostContextKeyStreamEndIndicator (131-131)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (27-42)
core/schemas/models.go (1)
  • Model (109-129)
core/schemas/context.go (2)
  • BifrostContext (32-42)
  • NewBifrostContext (47-65)
framework/streaming/types.go (1)
  • StreamResponseTypeFinal (24-24)
framework/tracing/llmspan.go (6)
core/schemas/bifrost.go (23)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • ChatCompletionRequest (95-95)
  • ChatCompletionStreamRequest (96-96)
  • TextCompletionRequest (93-93)
  • TextCompletionStreamRequest (94-94)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • TranscriptionStreamRequest (103-103)
  • SpeechRequest (100-100)
  • SpeechStreamRequest (101-101)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (9)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (216-216)
  • AttrErrorType (217-217)
  • AttrErrorCode (218-218)
  • AttrMaxTokens (163-163)
  • AttrTemperature (164-164)
  • AttrTopP (165-165)
  • AttrStopSequences (166-166)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • BifrostChatResponse (27-42)
core/schemas/textcompletions.go (2)
  • BifrostTextCompletionRequest (10-17)
  • BifrostTextCompletionResponse (69-77)
core/schemas/responses.go (2)
  • BifrostResponsesRequest (32-39)
  • BifrostResponsesResponse (45-85)
core/schemas/files.go (10)
  • BifrostFileUploadRequest (53-67)
  • BifrostFileListRequest (109-128)
  • BifrostFileRetrieveRequest (146-159)
  • BifrostFileDeleteRequest (186-198)
  • BifrostFileContentRequest (215-227)
  • BifrostFileUploadResponse (90-106)
  • BifrostFileListResponse (136-143)
  • BifrostFileRetrieveResponse (167-183)
  • BifrostFileDeleteResponse (206-212)
  • BifrostFileContentResponse (235-241)
transports/bifrost-http/server/server.go (4)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • Plugin (71-97)
  • ObservabilityPlugin (123-137)
transports/bifrost-http/handlers/middlewares.go (2)
  • TransportInterceptorMiddleware (50-70)
  • NewTracingMiddleware (207-215)
framework/tracing/store.go (1)
  • NewTraceStore (40-67)
framework/tracing/tracer.go (1)
  • NewTracer (24-29)
🔇 Additional comments (56)
plugins/otel/version (1)

1-1: LGTM!

Version bump to 1.0.52 is appropriate for the HTTPTransportMiddleware migration changes.

.github/workflows/release-pipeline.yml (2)

5-6: LGTM!

Adding v1.4.0 to the trigger branches is appropriate for releasing the new HTTPTransportMiddleware changes.


597-597: Good fix for dynamic branch reference.

Using ${{ github.ref_name }} instead of hardcoded main in the Discord notification correctly reflects the actual branch that triggered the pipeline.

ui/components/sidebar.tsx (2)

208-208: LGTM!

Styling update from rounded-md to rounded-sm is a minor visual refinement.


243-243: LGTM!

Consistent styling update to match the parent menu button's border radius.

core/changelog.md (2)

3-5: LGTM!

Feature additions are well documented and align with the PR objectives.


6-35: Well-documented breaking change with clear migration path.

The breaking changes section provides comprehensive guidance including:

  • Clear before/after API signatures
  • Key behavioral changes
  • Actionable migration steps
  • Reference to the migration guide

One note: The documentation references "core v1.3.0" (lines 12, 17), but the PR targets branch v1.4.0. Verify these version references are intentional and consistent with your versioning strategy.

plugins/jsonparser/main.go (2)

86-89: LGTM!

Correctly implements HTTPTransportMiddleware() returning nil since this plugin only processes responses via PostHook and doesn't need HTTP transport interception.


91-102: Minor documentation formatting update.

The added blank line in the doc comment is a minor formatting adjustment with no functional impact.

plugins/semanticcache/changelog.md (1)

1-18: LGTM!

Changelog comprehensively documents the breaking change from TransportInterceptor to HTTPTransportMiddleware with clear guidance for plugin consumers.

plugins/maxim/changelog.md (1)

4-19: LGTM!

Breaking changes documentation is consistent with other plugin changelogs and provides clear migration guidance.

plugins/jsonparser/changelog.md (1)

1-18: LGTM!

Changelog is well-structured and consistently documents the breaking change migration from TransportInterceptor to HTTPTransportMiddleware.

transports/bifrost-http/handlers/mcpserver.go (1)

73-77: LGTM!

The middleware type migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware is consistent with the PR-wide refactoring. The route registration logic remains unchanged.

core/providers/utils/utils.go (2)

887-899: LGTM!

The streaming chunk accumulation and deferred span completion logic is well-structured. The defensive nil checks before type assertions are appropriate.


1472-1484: Post-hook span finalization logic is well-designed.

The approach of injecting the deferred span ID into the context before calling the finalizer ensures post-hook spans are correctly parented under the LLM span. The fallback to the original context when no span ID exists is appropriate.

docs/plugins/migration-guide.mdx (1)

1-14: Well-structured migration guide.

The documentation clearly explains the API changes, provides before/after code examples, and includes helpful troubleshooting. The comparison table at lines 21-27 effectively summarizes the key differences.

transports/changelog.md (1)

9-31: Breaking changes are well-documented.

The changelog clearly communicates the API change, provides a migration summary with old/new signatures, and links to the detailed migration guide. This gives developers the information they need to update their plugins.

core/schemas/bifrost.go (2)

25-25: LGTM!

The Tracer field addition with the nil = NoOpTracer comment provides a clean opt-in mechanism for distributed tracing while maintaining backward compatibility.


143-149: Context keys are well-documented.

The new tracing context keys follow the established naming convention and have clear inline documentation explaining their purpose. The distinction between keys set by Bifrost vs. user-settable keys is clear from the comments.

framework/tracing/propagation.go (2)

10-56: LGTM!

The normalization functions properly handle UUID-to-W3C format conversion by stripping hyphens, enforcing lowercase, and validating hex characters. The truncation behavior for span IDs correctly handles longer inputs like UUIDs.


72-84: Implementation is correct per clarification.

Based on learnings, ExtractParentID intentionally returns ctx.TraceID because it extracts the trace ID from the parent request's traceparent header for trace continuation across service boundaries.

transports/bifrost-http/handlers/logging.go (1)

42-50: LGTM!

The middleware type migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware is consistent with the PR-wide refactoring pattern.

framework/streaming/transcription.go (1)

44-58: LGTM!

The Time to First Token (TTFT) calculation is correctly implemented and consistent with the pattern used in other streaming files (audio.go, chat.go, responses.go). The defensive check for non-zero timestamps before calculation prevents invalid TTFT values.

transports/bifrost-http/handlers/config.go (1)

55-55: LGTM! Middleware type migration aligns with plugin v2 architecture.

The migration from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware centralizes the middleware type definition in the schemas package, consistent with the broader architectural changes in this PR.

plugins/telemetry/changelog.md (1)

1-18: LGTM! Breaking changes are well-documented.

The changelog comprehensively documents the migration from TransportInterceptor to HTTPTransportMiddleware, including:

  • Clear API comparison (old vs new)
  • Consumer impact and required actions
  • Version information
  • Reference to the Plugin Migration Guide

This addresses the previous review feedback effectively.

plugins/governance/changelog.md (1)

1-21: LGTM! Comprehensive breaking change documentation.

The changelog provides thorough documentation of the plugin interface migration, consistent with other plugin changelogs in this PR. The breaking changes section clearly communicates the impact and migration steps.

plugins/logging/changelog.md (1)

1-19: LGTM! Breaking changes and feature improvements documented.

The changelog documents both the breaking API change and the feature improvement (central accumulator for memory optimization), providing complete information for plugin consumers.

transports/bifrost-http/handlers/inference.go (2)

365-365: LGTM! Middleware type migration consistent with plugin v2 architecture.

The change from lib.BifrostHTTPMiddleware to schemas.BifrostHTTPMiddleware aligns with the broader migration to centralize middleware types in the schemas package.


1072-1090: LGTM! Streaming trace completion logic is well-designed.

The deferred trace completion implementation correctly:

  • Signals to middleware that trace completion should be deferred (line 1074)
  • Retrieves the trace completer callback from context (line 1077)
  • Ensures w.Flush() completes before finalizing the trace (line 1084)
  • Gracefully handles missing trace completer with nil check (line 1087)

The type assertion at line 1077 silently discards the boolean, which is acceptable here since the nil check at line 1087 handles the case where the assertion fails or the value is not set.

Pattern observation: This ensures proper OTEL span lifecycle management where the parent trace (including llm.call spans) is only finalized after the entire streaming response has been written to the client.

plugins/mocker/main.go (1)

481-484: LGTM! Clean migration to HTTPTransportMiddleware.

The migration from TransportInterceptor to HTTPTransportMiddleware is correct. Returning nil is appropriate for this plugin since it doesn't need HTTP transport-level interception (it uses PreHook/PostHook for request/response mocking instead).

docs/plugins/writing-plugin.mdx (2)

65-241: LGTM! Excellent versioned documentation for the breaking change.

The documentation effectively communicates the plugin interface migration by:

  • Using tabs to provide version-specific examples (v1.4.x+ vs v1.3.x)
  • Showing complete code examples with the new HTTPTransportMiddleware pattern
  • Explaining the middleware composition pattern (calling next(ctx))
  • Including appropriate warnings about transport-specific behavior
  • Maintaining backward compatibility documentation for reference

This approach makes it easy for plugin developers to understand both the old and new patterns and migrate their plugins accordingly.


429-444: LGTM! Updated log examples reflect the new middleware pattern.

The log output examples correctly show HTTPTransportMiddleware called for v1.4.x+ and maintain the legacy TransportInterceptor called for v1.3.x, helping developers verify their plugin is working correctly after migration.

plugins/semanticcache/main.go (1)

338-341: LGTM! HTTPTransportMiddleware migration is correct.

The migration from TransportInterceptor to HTTPTransportMiddleware follows the same pattern as other plugins. Returning nil is appropriate since this plugin performs semantic caching via PreHook/PostHook and doesn't require HTTP transport-level interception.

plugins/logging/utils.go (1)

229-300: LGTM! Solid streaming conversion implementation.

The function correctly maps streaming accumulator results to processed stream responses with:

  • Proper nil handling
  • Comprehensive request type → stream type mapping with safe defaults
  • Correct response type determination based on finality
  • Safe tool calls extraction with nil guards
plugins/mocker/changelog.md (1)

1-18: Excellent changelog documentation.

The breaking changes section now comprehensively documents the TransportInterceptor → HTTPTransportMiddleware migration with clear comparison, consumer guidance, and migration references.

framework/streaming/accumulator.go (3)

104-118: LGTM! Proper timestamp initialization for TTFT.

The accumulator now correctly initializes both Timestamp and StartTimestamp to provide a baseline for TTFT calculations when chunks arrive.


138-141: LGTM! Consistent FirstChunkTimestamp tracking.

First chunk timestamp is now consistently captured across all streaming types (chat, transcription, audio, responses), enabling accurate TTFT calculations.

Also applies to: 162-165, 186-189, 210-213


384-392: LGTM! Thread-safe public accumulator creation.

The public CreateStreamAccumulator function properly uses locking when overriding the start timestamp, maintaining thread safety.

transports/bifrost-http/lib/middleware.go (1)

3-23: LGTM! Clean type migration to centralized schemas.

The middleware chaining logic is unchanged; only the type reference has been updated to use the centralized schemas.BifrostHTTPMiddleware definition.

framework/plugins/dynamicplugin_test.go (1)

54-84: LGTM! Test correctly validates new middleware API.

The test properly exercises the new HTTPTransportMiddleware flow:

  • Obtains middleware function from plugin
  • Wraps a next handler using standard middleware pattern
  • Uses fasthttp.RequestCtx as expected
  • Verifies next handler invocation
core/schemas/plugin.go (3)

36-38: LGTM! Standard middleware type definition.

The BifrostHTTPMiddleware type follows the standard fasthttp middleware pattern: receives next handler, returns wrapped handler.


75-79: LGTM! Clear middleware method documentation.

The HTTPTransportMiddleware method is well-documented with clear usage notes and transport-specific applicability.


109-137: LGTM! Clean observability extension pattern.

The ObservabilityPlugin interface extends Plugin and adds trace injection capability for observability backends. The documentation clearly explains async invocation and implementation requirements.

plugins/maxim/main.go (1)

124-127: LGTM! Appropriate nil return for unused middleware.

Returning nil from HTTPTransportMiddleware correctly indicates this plugin doesn't require HTTP transport interception.

plugins/governance/main.go (1)

258-309: Well-structured HTTP middleware implementation.

The middleware correctly:

  • Parses virtual key from multiple header sources
  • Validates virtual key state
  • Augments headers with MCP tool configurations
  • Performs load balancing with weighted provider selection
  • Marshals/unmarshals request body using sonic

The implementation follows the standard middleware pattern and integrates well with the governance store.

transports/bifrost-http/integrations/router.go (2)

69-73: Middleware type migration to schemas.BifrostHTTPMiddleware looks consistent

ExtensionRouter.RegisterRoutes and GenericRouter.RegisterRoutes now accept ...schemas.BifrostHTTPMiddleware and still chain via lib.ChainMiddlewares, which matches the new plugin v2 middleware API and the updated handlers/server wiring.

Also applies to: 318-367


1105-1121: Streaming trace deferral and completion wiring looks correct

Setting BifrostContextKeyDeferTraceCompletion and using a traceCompleter callback that runs after the SSE writer finishes ensures:

  • The tracing middleware doesn’t flush traces prematurely for streaming requests, and
  • Traces are completed only after the full stream (including final chunks) is written.

This aligns with the new tracing/observability design and keeps SSE semantics intact.

Also applies to: 1134-1143, 1336-1347

framework/tracing/store.go (1)

24-37: TraceStore concurrency and lifecycle management look solid

TraceStore uses sync.Map + pools for allocation, guards background cleanup with stopOnce, and cleanly shuts down via Stop(). Streaming helpers (AppendStreamingChunk / GetAccumulatedData) are nil‑safe and won’t panic even if no deferred span exists. This addresses the earlier double‑close panic risk while keeping the store safe for repeated shutdowns.

Also applies to: 39-67, 313-371

plugins/logging/main.go (1)

96-111: Logging plugin’s integration with central tracer and middleware API looks correct

  • HTTPTransportMiddleware correctly returns nil, so this plugin doesn’t participate in HTTP middleware chaining.
  • Streaming requests now create a stream accumulator via the tracer in PreHook, and PostHook delegates streaming aggregation to tracer.ProcessStreamingChunk, only updating logs on final chunks.
  • Cleanup of stream accumulators is explicitly left to the tracing middleware, while Cleanup() only stops the internal ticker/worker.

This matches the new plugin v2 + tracing design without introducing new lifecycle issues.

Also applies to: 113-156, 194-197, 225-233, 384-390, 423-470, 614-625

transports/bifrost-http/server/server.go (1)

29-29: HTTP middleware type migration and tracing bootstrap wiring look coherent

  • RegisterInferenceRoutes, RegisterAPIRoutes, and RegisterUIRoutes now consistently accept ...schemas.BifrostHTTPMiddleware, aligning with the new plugin v2 middleware API and updated handlers.
  • PrepareCommonMiddlewares returns []schemas.BifrostHTTPMiddleware and uses the Prometheus plugin’s HTTPMiddleware directly.
  • In Bootstrap, inference middlewares are composed as: tracing → transport interceptor → common (telemetry) → optional auth, with tracer initialization gated on observability/logging plugins and wired into the Bifrost client.

This is a clean integration of the new tracing + middleware architecture.

Also applies to: 939-947, 949-1033, 1035-1039, 1095-1107, 1213-1217, 1231-1233, 1236-1238, 1240-1264

transports/bifrost-http/handlers/middlewares.go (2)

3-18: Middleware type migration for CORS, plugin transport, and auth looks good

  • CorsMiddleware, TransportInterceptorMiddleware, and AuthMiddleware now all return schemas.BifrostHTTPMiddleware, matching the new core middleware type.
  • TransportInterceptorMiddleware correctly collects plugin.HTTPTransportMiddleware() values, skips nil, and chains them via lib.ChainMiddlewares while preserving plugin order.
  • Auth semantics remain unchanged while adopting the new middleware type.

No functional regressions spotted here.

Also applies to: 20-47, 49-70, 84-189


281-307: Trace completion and observability forwarding logic is well‑structured

completeAndFlushTrace correctly:

  • Cleans up the stream accumulator for the trace,
  • Calls EndTrace to obtain the completed trace,
  • Iterates observability plugins and calls Inject on a fresh background context, and
  • Returns the trace to the pool via ReleaseTrace.

GetObservabilityPlugins provides a simple helper for extracting observability plugins via type assertion. This matches the rest of the tracing/OTEL integration.

Also applies to: 309-329

framework/tracing/helpers.go (1)

1-83: Context helpers for trace/span operations are safe and minimal

These helpers:

  • Safely derive the current trace ID from context,
  • No‑op when there is no trace ID or trace/span present, and
  • Delegate span creation/ending/attribute/event operations to TraceStore.

This keeps call sites simple while avoiding panics when tracing is disabled or unavailable.

plugins/otel/converter.go (1)

72-87: OTEL trace/span conversion is comprehensive and type‑safe

  • convertTraceToResourceSpan builds a single ResourceSpan with appropriate resource attributes and instrumentation scope, converting all Bifrost spans via convertSpanToOTELSpan.
  • convertSpanToOTELSpan correctly maps trace/span IDs, timestamps, attributes, events, status, and parent span ID.
  • anyToKeyValue and convertAttributesToKeyValues handle common scalar, slice, and map types while skipping nil/empty values, and convertSpanKind / convertSpanStatus / convertSpanEvents map Bifrost enums and events into the corresponding OTEL representations.

This should produce well‑formed OTLP traces for downstream observability backends.

Also applies to: 90-110, 112-131, 133-229, 231-257, 259-269, 271-285

core/schemas/tracer.go (1)

13-116: Tracing core abstractions look consistent and safe

The Tracer interface, StreamAccumulatorResult, and NoOpTracer are well-aligned with how tracing is consumed elsewhere (Bifrost + framework tracer). No-op implementations avoid panics even when spans/handles are nil, and the interface surface is coherent for both streaming and non‑streaming flows.

Also applies to: 118-187

framework/tracing/llmspan.go (1)

377-383: The current implementation is correct—no change needed

The Embedding field in EmbeddingInput is typed as []int, not []float64. The %d format specifier is appropriate for integer values. The suggested change to %g is not applicable here.

Likely an incorrect or invalid review comment.

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 18b4142 to 4d9d0fc Compare December 24, 2025 14:06
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 9

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
ui/components/ui/switch.tsx (1)

1-36: Address breaking changes to Switch component styling.

This PR includes changes to the Switch component's default size and border-radius styling:

  1. Default size change (line 13): Changing the default from "default" to "md" is a breaking change. Any existing <Switch> instances without an explicit size prop will render smaller than before.
  2. Border radius change (lines 16, 26): Changing from rounded-full to rounded-sm changes the visual appearance from fully rounded to slightly rounded corners. This is a breaking visual change across all Switch instances.

Both changes should be evaluated for backward compatibility impact. If these changes are intentional as part of the larger refactor, consider documenting the breaking changes in a changelog or migration guide.

♻️ Duplicate comments (12)
ui/components/ui/switch.tsx (1)

13-13: Breaking change: default size reduction affects existing usages.

As flagged in the previous review, changing the default from "default" to "md" will cause all existing Switch components without an explicit size prop to render smaller (h-5 w-9 instead of h-6 w-11). This is a breaking visual change that impacts multiple files across the codebase.

🔎 Revert to preserve existing behavior
-	({ className, size = "md", ...props }, ref) => (
+	({ className, size = "default", ...props }, ref) => (

Alternatively, if the smaller default is intentional, audit all Switch usages and explicitly add size="default" where the larger appearance must be preserved.

plugins/mocker/version (1)

1-1: Version inconsistency was previously flagged.

The concern about patch-level bump for a breaking change was already raised in a prior review. See the existing comment for details on aligning with semantic versioning.

transports/changelog.md (1)

16-23: Clarify version boundaries for the API migration.

The version labels "v1.3.x (removed)" and "v1.4.x+ (new)" conflict with other changelogs that indicate the new HTTPTransportMiddleware API was introduced in core v1.3.0. This inconsistency may confuse developers about which versions support which API.

If the new API is available in v1.3.0+, consider updating the labels to reflect that TransportInterceptor was removed in v1.3.0 (not v1.3.x) and the new API is available in v1.3.0+ (not v1.4.x+). Alternatively, if v1.4.x truly introduces the breaking change, clarify this across all changelogs.

🔎 Suggested version alignment

Based on the pattern in plugin changelogs referencing "core v1.3.0 and framework v1.2.0":

   **Migration summary:**
-  ```
-  // v1.3.x (removed)
+  ```text
+  // v1.2.x and earlier (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)
 
-  // v1.4.x+ (new)
+  // v1.3.0+ (new)
   HTTPTransportMiddleware() BifrostHTTPMiddleware
   // where BifrostHTTPMiddleware = func(next fasthttp.RequestHandler) fasthttp.RequestHandler

</details>

</blockquote></details>
<details>
<summary>transports/bifrost-http/integrations/router.go (1)</summary><blockquote>

`502-607`: **Non‑streaming handlers should use the converted Bifrost context, not `*fasthttp.RequestCtx`.**

In `handleNonStreamingRequest`, all Bifrost client calls still pass `ctx` (a `*fasthttp.RequestCtx`) instead of the cancellable/enriched context from `ConvertToBifrostContext`:

```go
listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)

This contradicts the comment about using the cancellable context, and (assuming the Bifrost client expects context.Context) is both a compile‑time type mismatch and a functional break for tracing, plugin context, and timeouts.

These calls should use *bifrostCtx (or a local requestCtx := *bifrostCtx) exactly like the batch/file handlers:

Suggested fix
-func (g *GenericRouter) handleNonStreamingRequest(ctx *fasthttp.RequestCtx, config RouteConfig, req interface{}, bifrostReq *schemas.BifrostRequest, bifrostCtx *context.Context) {
-	// Use the cancellable context from ConvertToBifrostContext
+func (g *GenericRouter) handleNonStreamingRequest(ctx *fasthttp.RequestCtx, config RouteConfig, req interface{}, bifrostReq *schemas.BifrostRequest, bifrostCtx *context.Context) {
+	// Use the cancellable context from ConvertToBifrostContext
@@
-	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+	case bifrostReq.ListModelsRequest != nil:
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)
@@
-	case bifrostReq.TextCompletionRequest != nil:
-		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+	case bifrostReq.TextCompletionRequest != nil:
+		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(*bifrostCtx, bifrostReq.TextCompletionRequest)
@@
-	case bifrostReq.ChatRequest != nil:
-		chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+	case bifrostReq.ChatRequest != nil:
+		chatResponse, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq.ChatRequest)
@@
-	case bifrostReq.ResponsesRequest != nil:
-		responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+	case bifrostReq.ResponsesRequest != nil:
+		responsesResponse, bifrostErr := g.client.ResponsesRequest(*bifrostCtx, bifrostReq.ResponsesRequest)
@@
-	case bifrostReq.EmbeddingRequest != nil:
-		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+	case bifrostReq.EmbeddingRequest != nil:
+		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(*bifrostCtx, bifrostReq.EmbeddingRequest)
@@
-	case bifrostReq.SpeechRequest != nil:
-		speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+	case bifrostReq.SpeechRequest != nil:
+		speechResponse, bifrostErr := g.client.SpeechRequest(*bifrostCtx, bifrostReq.SpeechRequest)
@@
-	case bifrostReq.TranscriptionRequest != nil:
-		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+	case bifrostReq.TranscriptionRequest != nil:
+		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(*bifrostCtx, bifrostReq.TranscriptionRequest)

Also applies to: 660-693

core/providers/utils/utils.go (1)

1412-1501: Pass context.Context by value in completeDeferredSpan (repeat of earlier review).

completeDeferredSpan(ctx *context.Context, ...) is still taking a pointer to context.Context and then dereferencing for every Value lookup. Go convention (and context.Context’s design) is to always pass contexts by value; using a pointer buys nothing and complicates call sites (completeDeferredSpan(&ctx, ...)).

This was already flagged previously; you can simplify and make the API more idiomatic by:

  • Changing the signature to func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError).
  • Removing the nil‑pointer check and using ctx.Value(...) directly.
  • Updating all callers to pass ctx (not &ctx).
Proposed refactor
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
-		return
-	}
-
-	// Get the trace ID from context (this IS available in the provider's goroutine)
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	// Get the trace ID from context (this IS available in the provider's goroutine)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
@@
-	tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+	tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
@@
-	if finalizer, ok := (*ctx).Value(schemas.BifrostContextKeyPostHookSpanFinalizer).(func(context.Context)); ok && finalizer != nil {
+	if finalizer, ok := ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer).(func(context.Context)); ok && finalizer != nil {
@@
-			finalizerCtx := context.WithValue(*ctx, schemas.BifrostContextKeySpanID, spanID)
+			finalizerCtx := context.WithValue(ctx, schemas.BifrostContextKeySpanID, spanID)
 			finalizer(finalizerCtx)
 		} else {
-			finalizer(*ctx)
+			finalizer(ctx)
 		}
 	}
@@
-	tracer.ClearDeferredSpan(traceID)
+	tracer.ClearDeferredSpan(traceID)

And adjust call sites, e.g.:

-            completeDeferredSpan(&ctx, response, nil)
+            completeDeferredSpan(ctx, response, nil)
-            completeDeferredSpan(&ctx, nil, bifrostErr)
+            completeDeferredSpan(ctx, nil, bifrostErr)
docs/plugins/migration-guide.mdx (1)

247-250: Ensure @v1.4.0 exists before publishing this command.

The migration guide suggests:

go get github.com/maximhq/bifrost/[email protected]

If the v1.4.0 tag is not created by the time this guide is used, this command will fail for users. Either:

  • Guarantee that v1.4.0 is tagged before releasing these docs, or
  • Update the example to reference an existing tag or a commit hash, and adjust when the tag is cut.
examples/plugins/hello-world/main.go (1)

19-27: Example no longer demonstrates transport→hook context propagation.

HTTPTransportMiddleware stores "hello-world-plugin-transport-interceptor" as a user value on *fasthttp.RequestCtx, but PreHook and PostHook read that key from *schemas.BifrostContext. Without explicitly copying this value into the Bifrost context, value1 will always log nil, which undercuts the example’s intent.

Either:

  • Bridge the value into BifrostContext (e.g., fetch *schemas.BifrostContext from ctx.UserValue and call SetValue), or
  • Remove the value1 reads from PreHook/PostHook if you don’t want to demo this propagation anymore.

Also applies to: 29-44

plugins/governance/main.go (2)

233-256: Validate x-bf-vk header with VirtualKeyPrefix like other headers.

parseVirtualKey currently returns the x-bf-vk header value verbatim:

vkHeader := ctx.Request.Header.Peek("x-bf-vk")
if string(vkHeader) != "" {
	return bifrost.Ptr(string(vkHeader))
}

but the Authorization and x-api-key paths both enforce VirtualKeyPrefix on the key. This inconsistency allows arbitrary x-bf-vk values to be treated as virtual keys, bypassing the prefix guard used elsewhere.

You should normalize and enforce the prefix for x-bf-vk as well, e.g.:

Suggested fix
 func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
 	var virtualKeyValue string
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
-	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
-	}
+	if raw := strings.TrimSpace(string(vkHeader)); raw != "" {
+		if strings.HasPrefix(strings.ToLower(raw), VirtualKeyPrefix) {
+			return bifrost.Ptr(raw)
+		}
+	}
@@
-	xAPIKey := string(ctx.Request.Header.Peek("x-api-key"))
-	if xAPIKey != "" && strings.HasPrefix(strings.ToLower(xAPIKey), VirtualKeyPrefix) {
-		return bifrost.Ptr(xAPIKey)
+	xAPIKey := strings.TrimSpace(string(ctx.Request.Header.Peek("x-api-key")))
+	if xAPIKey != "" && strings.HasPrefix(strings.ToLower(xAPIKey), VirtualKeyPrefix) {
+		return bifrost.Ptr(xAPIKey)
 	}
 	return nil
 }

258-307: Fix log messages around JSON (un)marshaling in HTTP middleware.

Within HTTPTransportMiddleware, both error logs say “failed to marshal request body to check for virtual key”:

err = sonic.Unmarshal(ctx.Request.Body(), &payload)
if err != nil {
    p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
    ...
}
...
body, err := sonic.Marshal(payload)
if err != nil {
    p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
    ...
}

The first is an unmarshal failure, and by the second point you’re no longer “checking for virtual key” but rebuilding the body after governance‑driven changes.

Consider tightening these messages for accuracy:

Suggested wording
-	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
-	if err != nil {
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+	if err != nil {
+		p.logger.Error("failed to unmarshal request body for governance HTTP middleware: %v", err)
@@
-	body, err := sonic.Marshal(payload)
-	if err != nil {
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+	body, err := sonic.Marshal(payload)
+	if err != nil {
+		p.logger.Error("failed to marshal updated request body after governance routing: %v", err)
core/schemas/trace.go (1)

40-49: Guard Trace.Reset, Span.End, and Span.Reset with the same mutex used for other mutations.

Trace.AddSpan/GetSpan and Span.SetAttribute/AddEvent use mu, but:

  • Trace.Reset mutates Spans and other fields without locking.
  • Span.End writes EndTime, Status, and StatusMsg without locking.
  • Span.Reset clears all fields without locking.

If these are ever called while other goroutines still hold references (e.g., via a pool or concurrent readers), you get data races despite the mutex presence.

Align them with the rest of the API by locking around the mutations:

Proposed adjustment
 func (t *Trace) Reset() {
-	t.TraceID = ""
-	t.ParentID = ""
-	t.RootSpan = nil
-	t.Spans = t.Spans[:0]
-	t.StartTime = time.Time{}
-	t.EndTime = time.Time{}
-	t.Attributes = nil
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.TraceID = ""
+	t.ParentID = ""
+	t.RootSpan = nil
+	t.Spans = t.Spans[:0]
+	t.StartTime = time.Time{}
+	t.EndTime = time.Time{}
+	t.Attributes = nil
 }

 func (s *Span) End(status SpanStatus, statusMsg string) {
-	s.EndTime = time.Now()
-	s.Status = status
-	s.StatusMsg = statusMsg
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.EndTime = time.Now()
+	s.Status = status
+	s.StatusMsg = statusMsg
 }

 func (s *Span) Reset() {
-	s.SpanID = ""
-	s.ParentID = ""
-	s.TraceID = ""
-	s.Name = ""
-	s.Kind = SpanKindUnspecified
-	s.StartTime = time.Time{}
-	s.EndTime = time.Time{}
-	s.Status = SpanStatusUnset
-	s.StatusMsg = ""
-	s.Attributes = nil
-	s.Events = s.Events[:0]
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.SpanID = ""
+	s.ParentID = ""
+	s.TraceID = ""
+	s.Name = ""
+	s.Kind = SpanKindUnspecified
+	s.StartTime = time.Time{}
+	s.EndTime = time.Time{}
+	s.Status = SpanStatusUnset
+	s.StatusMsg = ""
+	s.Attributes = nil
+	s.Events = s.Events[:0]
 }

Also applies to: 84-104

framework/tracing/tracer.go (1)

393-455: Make choice and tool‑call ordering deterministic in buildCompleteResponseFromChunks.

choiceMap and each acc.toolCalls are maps; you build choiceIndices and tcIndices slices but never sort them, so:

  • Choice order in result.ChatResponse.Choices is nondeterministic.
  • Tool‑call order per message is also nondeterministic.

The comments already say “Sort … for deterministic output”, so this looks like an omission.

Add sorting before iteration:

Proposed fix
 	// Build final choices from accumulated data
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)
@@
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)

Also applies to: 433-441

framework/tracing/llmspan.go (1)

190-193: Namespace ExtraParams attributes to avoid collisions with standard keys.

Across multiple helpers, ExtraParams are written directly into attrs:

  • Chat/Text/Embedding/Responses requests
  • Batch and file request types

This allows user‑supplied keys to overwrite standard ones (e.g., gen_ai.request.model), which can corrupt telemetry.

Consider prefixing these keys consistently, e.g.:

const extraPrefix = "gen_ai.extra."

for k, v := range req.Params.ExtraParams {
    attrs[extraPrefix+k] = fmt.Sprintf("%v", v)
}

and apply the same pattern to all ExtraParams loops.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 769-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029

🧹 Nitpick comments (4)
transports/bifrost-http/lib/middleware.go (1)

19-21: Consider guarding against nil middlewares.

If a plugin returns nil from HTTPTransportMiddleware(), calling middlewares[i](chained) will panic. Consider filtering nil entries or documenting that callers must exclude nil middlewares before chaining.

🔎 Proposed fix to guard against nil middlewares
 func ChainMiddlewares(handler fasthttp.RequestHandler, middlewares ...schemas.BifrostHTTPMiddleware) fasthttp.RequestHandler {
 	// If no middlewares, return the original handler
 	if len(middlewares) == 0 {
 		return handler
 	}
 	// Build the chain from right to left (last middleware wraps the handler)
 	// This ensures execution order is left to right (first middleware executes first)
 	chained := handler
 	for i := len(middlewares) - 1; i >= 0; i-- {
+		if middlewares[i] == nil {
+			continue
+		}
 		chained = middlewares[i](chained)
 	}
 	return chained
 }
plugins/maxim/main.go (1)

124-127: No‑op HTTP middleware is fine; ensure callers handle nil safely.

For Maxim, returning nil from HTTPTransportMiddleware is a reasonable way to opt out of HTTP‑layer interception, given all useful work happens in PreHook/PostHook.

Just ensure the middleware wiring (lib.ChainMiddlewares / router registration) checks for nil before invoking middleware functions so a nil return doesn’t result in a panic when chaining.

plugins/governance/main.go (1)

42-49: BaseGovernancePlugin still exposes TransportInterceptor; consider updating or removing.

BaseGovernancePlugin continues to declare:

type BaseGovernancePlugin interface {
	GetName() string
	TransportInterceptor(...)
	PreHook(...)
	PostHook(...)
	Cleanup() error
	GetGovernanceStore() GovernanceStore
}

while the concrete GovernancePlugin now implements HTTPTransportMiddleware() instead and no longer provides TransportInterceptor. If anything in the codebase still relies on BaseGovernancePlugin, this mismatch will either cause compile‑time errors when used with GovernancePlugin or mislead future contributors about the supported transport hook.

If BaseGovernancePlugin is no longer used, it’s better to remove it; otherwise, update it to expose HTTPTransportMiddleware() instead of TransportInterceptor to stay in sync with the new plugin API.

Also applies to: 258-307

framework/tracing/helpers.go (1)

58-69: Consider nil store check for consistency.

While other functions implicitly handle nil store via GetTrace returning nil, SetSpanAttribute and AddSpanEvent would benefit from an explicit nil store check for clarity.

🔎 Optional improvement
 func SetSpanAttribute(ctx context.Context, store *TraceStore, spanID, key string, value any) {
+	if store == nil {
+		return
+	}
 	trace := GetTrace(ctx, store)
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 18b4142 and 4d9d0fc.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (89)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (2)
  • plugins/semanticcache/version
  • plugins/maxim/version
🚧 Files skipped from review as they are similar to previous changes (27)
  • .github/workflows/scripts/release-bifrost-http.sh
  • transports/bifrost-http/handlers/ui.go
  • framework/streaming/types.go
  • transports/bifrost-http/handlers/config.go
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • transports/bifrost-http/handlers/cache.go
  • core/bifrost_test.go
  • core/schemas/context.go
  • framework/version
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/websocket.go
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/governance.go
  • plugins/governance/version
  • plugins/logging/utils.go
  • plugins/semanticcache/main.go
  • plugins/jsonparser/version
  • plugins/logging/version
  • transports/version
  • framework/plugins/dynamicplugin_test.go
  • docs/plugins/getting-started.mdx
  • plugins/logging/changelog.md
  • transports/bifrost-http/handlers/middlewares_test.go
  • ui/components/sidebar.tsx
  • plugins/otel/version
  • core/version
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/maxim/changelog.md
  • plugins/mocker/changelog.md
  • plugins/maxim/main.go
  • docs/plugins/writing-plugin.mdx
  • framework/streaming/transcription.go
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/semanticcache/changelog.md
  • core/schemas/plugin.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/providers.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/middlewares.go
  • plugins/governance/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • plugins/logging/main.go
  • core/changelog.md
  • framework/tracing/propagation.go
  • plugins/telemetry/main.go
  • ui/components/ui/switch.tsx
  • plugins/jsonparser/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/logging.go
  • plugins/telemetry/changelog.md
  • plugins/telemetry/version
  • plugins/otel/main.go
  • framework/streaming/chat.go
  • core/schemas/trace.go
  • plugins/jsonparser/changelog.md
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • plugins/mocker/version
  • docs/plugins/migration-guide.mdx
  • core/bifrost.go
  • core/schemas/tracer.go
  • framework/streaming/accumulator.go
  • plugins/otel/changelog.md
  • transports/bifrost-http/handlers/health.go
  • framework/tracing/helpers.go
  • plugins/otel/converter.go
  • core/schemas/bifrost.go
  • framework/tracing/store.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • core/providers/utils/utils.go
  • docs/docs.json
  • ui/components/ui/sheet.tsx
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • plugins/governance/main.go
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • plugins/maxim/main.go
  • framework/streaming/transcription.go
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/middleware.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/logging/main.go
  • framework/tracing/propagation.go
  • plugins/telemetry/main.go
  • plugins/jsonparser/main.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/logging.go
  • plugins/otel/main.go
  • framework/streaming/chat.go
  • core/schemas/trace.go
  • examples/plugins/hello-world/main.go
  • core/bifrost.go
  • core/schemas/tracer.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/health.go
  • framework/tracing/helpers.go
  • plugins/otel/converter.go
  • core/schemas/bifrost.go
  • framework/tracing/store.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • core/providers/utils/utils.go
  • transports/bifrost-http/server/server.go
  • plugins/governance/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
🧬 Code graph analysis (30)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/trace.go (1)
  • Trace (10-19)
ui/app/workspace/logs/views/logDetailsSheet.tsx (4)
ui/components/ui/sheet.tsx (1)
  • SheetContent (137-137)
ui/components/ui/alertDialog.tsx (9)
  • AlertDialog (83-83)
  • AlertDialogTrigger (93-93)
  • AlertDialogContent (86-86)
  • AlertDialogHeader (89-89)
  • AlertDialogTitle (92-92)
  • AlertDialogDescription (87-87)
  • AlertDialogFooter (88-88)
  • AlertDialogCancel (85-85)
  • AlertDialogAction (84-84)
ui/components/ui/dropdownMenu.tsx (5)
  • DropdownMenu (193-193)
  • DropdownMenuTrigger (207-207)
  • DropdownMenuContent (195-195)
  • DropdownMenuItem (197-197)
  • DropdownMenuSeparator (202-202)
ui/components/ui/button.tsx (1)
  • Button (70-70)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/integrations/router.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (6)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/inference.go (5)
  • ChatRequest (172-176)
  • ResponsesRequest (266-270)
  • EmbeddingRequest (273-277)
  • SpeechRequest (279-283)
  • TranscriptionRequest (285-289)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/plugins.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (5)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
plugins/logging/main.go (3)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/bifrost.go (4)
  • RequestType (89-89)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyTraceID (143-143)
  • TranscriptionRequest (102-102)
core/schemas/tracer.go (1)
  • Tracer (38-116)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (4)
core/schemas/plugin.go (1)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/bifrost.go (3)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
core/schemas/trace.go (1)
  • Trace (10-19)
framework/streaming/chat.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/schemas/trace.go (1)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (14)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanEvent (107-111)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/store.go (2)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
framework/tracing/llmspan.go (8)
core/schemas/bifrost.go (18)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • TextCompletionRequest (93-93)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • SpeechRequest (100-100)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (34)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (216-216)
  • AttrErrorType (217-217)
  • AttrErrorCode (218-218)
  • AttrVirtualKeyID (228-228)
  • AttrVirtualKeyName (229-229)
  • AttrSelectedKeyID (230-230)
  • AttrSelectedKeyName (231-231)
  • AttrTeamID (232-232)
  • AttrTeamName (233-233)
  • AttrCustomerID (234-234)
  • AttrCustomerName (235-235)
  • AttrNumberOfRetries (236-236)
  • AttrFallbackIndex (237-237)
  • AttrMaxTokens (163-163)
  • AttrTemperature (164-164)
  • AttrTopP (165-165)
  • AttrStopSequences (166-166)
  • AttrPresencePenalty (167-167)
  • AttrFrequencyPenalty (168-168)
  • AttrParallelToolCall (169-169)
  • AttrRequestUser (170-170)
  • AttrInputMessages (222-222)
  • AttrResponseID (191-191)
  • AttrResponseModel (192-192)
  • AttrObject (197-197)
  • AttrSystemFprint (194-194)
  • AttrCreated (196-196)
  • AttrServiceTier (195-195)
  • AttrOutputMessages (225-225)
  • AttrFinishReason (193-193)
  • AttrN (175-175)
  • AttrUsageCost (213-213)
core/schemas/chatcompletions.go (1)
  • BifrostChatRequest (12-19)
core/schemas/textcompletions.go (2)
  • BifrostTextCompletionRequest (10-17)
  • BifrostTextCompletionResponse (69-77)
core/schemas/embedding.go (2)
  • BifrostEmbeddingRequest (9-16)
  • BifrostEmbeddingResponse (22-28)
core/schemas/responses.go (2)
  • BifrostResponsesRequest (32-39)
  • BifrostResponsesResponse (45-85)
core/schemas/batch.go (5)
  • BifrostBatchCreateRequest (65-83)
  • BifrostBatchListRequest (118-133)
  • BifrostBatchRetrieveRequest (150-159)
  • BifrostBatchCancelRequest (205-214)
  • BifrostBatchResultsRequest (234-246)
core/schemas/files.go (5)
  • BifrostFileUploadRequest (53-67)
  • BifrostFileListRequest (109-128)
  • BifrostFileRetrieveRequest (146-159)
  • BifrostFileDeleteRequest (186-198)
  • BifrostFileContentRequest (215-227)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 4d9d0fc to 810ac1d Compare December 24, 2025 15:20
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/lib/config.go (1)

400-409: Dereference PricingSyncInterval safely and apply defaults.

In initFrameworkConfigFromFile, the else-if branch dereferences configData.FrameworkConfig.Pricing.PricingSyncInterval without first checking if it is nil:

syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
pricingConfig.PricingSyncInterval = &syncDuration

If pricing_sync_interval is omitted in the JSON config, this will panic. Additionally, the code should fall back to sensible defaults when no pricing configuration is available, similar to initDefaultFrameworkConfig:

if frameworkConfig != nil && frameworkConfig.PricingSyncInterval != nil && *frameworkConfig.PricingSyncInterval > 0 {
  syncDuration := time.Duration(*frameworkConfig.PricingSyncInterval) * time.Second
  pricingConfig.PricingSyncInterval = &syncDuration
} else {
  pricingConfig.PricingSyncInterval = bifrost.Ptr(modelcatalog.DefaultPricingSyncInterval)
}

Add nil checks before dereferencing and apply the same default fallback pattern used elsewhere.

core/bifrost.go (1)

3034-3072: ProcessAndSendError missing pipeline release for streaming error paths.

The streaming pipeline lifecycle has a resource leak:

When a scanner error occurs during streaming (e.g., network issues), ProcessAndSendError() is called but it does not invoke completeDeferredSpan(), which is responsible for calling the stored finalizer and releasing the pipeline.

Leak scenario:

  1. Pipeline created and finalizer stored in context (lines 3037–3052)
  2. Scanner error occurs during streaming → ProcessAndSendError() called
  3. ProcessAndSendError() has no completeDeferredSpan() call → finalizer never invoked
  4. Back in bifrost.go (line 3070): bifrostError is nil (error was sent through channel, not returned), so the release condition fails
  5. Pipeline is never released

Solution: Add the same completeDeferredSpan() check to ProcessAndSendError() that exists in ProcessAndSendBifrostError() (lines 941–946 in core/providers/utils/utils.go).

♻️ Duplicate comments (15)
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

183-187: Add aria-label for accessibility.

The icon-only button should include an aria-label to provide context for screen reader users. This was flagged in a previous review.

🔎 Suggested fix
 <DropdownMenuTrigger asChild>
-	<Button variant="ghost" size="icon">
+	<Button variant="ghost" size="icon" aria-label="Log actions">
 		<MoreVertical className="h-3 w-3" />
 	</Button>
 </DropdownMenuTrigger>
framework/tracing/llmspan.go (2)

190-193: ExtraParams keys can collide with standard attribute names.

User-provided keys from ExtraParams are written directly to the attributes map. If a user sets a key like "gen_ai.request.model", it would overwrite the standard attribute. This pattern repeats throughout the file (lines 297-300, 364-367, 574-577, 707-710, 737-740, 752-755, 767-770, 782-785, 957-960, 981-984, 996-999, 1011-1014, 1026-1029).


376-382: Use a float-appropriate formatter for embedding values.

The code uses %d (integer format) for embedding values, but embeddings are typically float slices. This will truncate values or produce incorrect output.

core/providers/utils/utils.go (3)

880-925: Defer span completion until after post-hooks for accurate tracing.

The current implementation calls completeDeferredSpan (line 897) before postHookRunner (line 902), which means:

  • LLM span attributes reflect the pre-post-hook response, not what the client actually received
  • Post-hook span aggregation completes before the final PostHook runs for the last chunk

For accurate end-to-end tracing, restructure to:

  1. Run postHookRunner first to get processedResponse and processedError
  2. Send the processed result to responseChan
  3. Then call completeDeferredSpan with the processed values if this is the final chunk

This ensures spans capture the fully processed streaming response.

🔎 Suggested restructuring
 func ProcessAndSendResponse(...) {
 	// Accumulate chunk for tracing (common for all providers)
 	if tracer, ok := ctx.Value(schemas.BifrostContextKeyTracer).(schemas.Tracer); ok && tracer != nil {
 		if traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string); ok && traceID != "" {
 			tracer.AddStreamingChunk(traceID, response)
 		}
 	}
 
-	// Check if this is the final chunk and complete deferred span if so
-	if isFinalChunk := ctx.Value(schemas.BifrostContextKeyStreamEndIndicator); isFinalChunk != nil {
-		if final, ok := isFinalChunk.(bool); ok && final {
-			completeDeferredSpan(&ctx, response, nil)
-		}
-	}
-
 	// Run post hooks on the response
 	processedResponse, processedError := postHookRunner(&ctx, response, nil)
 
 	if HandleStreamControlSkip(processedError) {
 		return
 	}
+
+	// Complete deferred span AFTER post-hooks with processed values
+	if isFinalChunk := ctx.Value(schemas.BifrostContextKeyStreamEndIndicator); isFinalChunk != nil {
+		if final, ok := isFinalChunk.(bool); ok && final {
+			completeDeferredSpan(&ctx, processedResponse, processedError)
+		}
+	}
 
 	streamResponse := &schemas.BifrostStream{}
 	// ... rest of function
 }

932-969: Apply the same fix: defer span completion until after post-hooks.

ProcessAndSendBifrostError has the same ordering issue as ProcessAndSendResponse: completeDeferredSpan (line 942) is called before postHookRunner (line 947).

Apply the same restructuring here to ensure error spans reflect the post-processed error state.


1416-1501: Pass context by value, not by pointer (Go convention).

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. Contexts should always be passed by value, never by pointer:

  • context.Context is designed for safe concurrent use when passed by value
  • Passing by pointer is an anti-pattern in Go

Update the signature and all call sites:

🔎 Proposed fix
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == nil || ctx == context.Background() || ctx == context.TODO() {
 		return
 	}
 
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
 	if !ok || traceID == "" {
 		return
 	}
 
-	tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+	tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
 	// ... rest of function with ctx instead of (*ctx)

Update all call sites:

-completeDeferredSpan(&ctx, response, nil)
+completeDeferredSpan(ctx, response, nil)
examples/plugins/hello-world/main.go (1)

19-44: Fix broken context value propagation in example.

The middleware sets a value on fasthttp.RequestCtx (line 23) but PreHook (line 30) and PostHook (line 39) try to read it from *schemas.BifrostContext. These are different context objects, so the value will always be nil.

To demonstrate proper context value flow between middleware and hooks:

Option 1: Remove the context reads from PreHook/PostHook if propagation demo is no longer needed:

🔎 Option 1: Remove broken context reads
 func PreHook(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.PluginShortCircuit, error) {
-	value1 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-transport-interceptor"))
-	fmt.Println("value1:", value1)
 	ctx.SetValue(schemas.BifrostContextKey("hello-world-plugin-pre-hook"), "pre-hook-value")
 	fmt.Println("PreHook called")
 	return req, nil, nil
 }

 func PostHook(ctx *schemas.BifrostContext, resp *schemas.BifrostResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) {
 	fmt.Println("PostHook called")
-	value1 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-transport-interceptor"))
-	fmt.Println("value1:", value1)
 	value2 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-pre-hook"))
 	fmt.Println("value2:", value2)
 	return resp, bifrostErr, nil
 }

Option 2: Document that middleware and core hooks use different contexts (if this is intentional to show the separation of concerns).

transports/changelog.md (1)

11-31: Add language specifier to code block and verify version references.

Two minor issues to address:

  1. Markdown linting: The code block at line 16 needs a language specifier (e.g., go or text) to fix the MD040 violation.

  2. Version consistency: The migration summary shows "v1.3.x (removed)" and "v1.4.x+ (new)", but the core changelog (core/changelog.md) indicates the new API is available in "core v1.3.0+". Please verify and align version references across both changelogs to avoid developer confusion.

🔎 Suggested fix for markdown
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)
transports/bifrost-http/integrations/router.go (1)

502-693: Non-streaming handlers using wrong context (duplicate critical issue).

Lines 514, 534, 557, 580, 603, 626, and 660 pass the raw *fasthttp.RequestCtx (ctx) to Bifrost client methods instead of the converted/cancellable Bifrost context (*bifrostCtx). This breaks:

  1. Tracing and plugin context propagation (all BifrostContextKey* values live on bifrostCtx)
  2. Potential context cancellation
  3. May cause type mismatches if Bifrost methods expect context.Context

The comment on line 503 explicitly says "Use the cancellable context from ConvertToBifrostContext", but the code doesn't follow it.

🔎 Proposed fix for all non-streaming calls
 	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)
 
 	case bifrostReq.TextCompletionRequest != nil:
-		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(*bifrostCtx, bifrostReq.TextCompletionRequest)
 
 	case bifrostReq.ChatRequest != nil:
-		chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+		chatResponse, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq.ChatRequest)
 
 	case bifrostReq.ResponsesRequest != nil:
-		responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+		responsesResponse, bifrostErr := g.client.ResponsesRequest(*bifrostCtx, bifrostReq.ResponsesRequest)
 
 	case bifrostReq.EmbeddingRequest != nil:
-		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(*bifrostCtx, bifrostReq.EmbeddingRequest)
 
 	case bifrostReq.SpeechRequest != nil:
-		speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+		speechResponse, bifrostErr := g.client.SpeechRequest(*bifrostCtx, bifrostReq.SpeechRequest)
 
 	case bifrostReq.TranscriptionRequest != nil:
-		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(*bifrostCtx, bifrostReq.TranscriptionRequest)
plugins/governance/main.go (2)

233-256: Missing prefix validation for x-bf-vk header (duplicate issue).

The x-bf-vk header on lines 236-238 is returned without validating that it starts with VirtualKeyPrefix, while the Authorization (Bearer) and x-api-key headers on lines 243 and 252 correctly validate the prefix. This inconsistency could allow invalid virtual keys to pass through when provided via the x-bf-vk header.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
-			return bifrost.Ptr(string(vkHeader))
+			return bifrost.Ptr(vkValue)
+		}
 	}

287-292: Fix error message accuracy (duplicate issue).

Line 289: The error message says "failed to marshal" but the operation is unmarshaling the request body.

🔎 Proposed fix
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+		p.logger.Error("failed to unmarshal request body: %v", err)
framework/plugins/dynamicplugin.go (1)

140-147: Type mismatch: dynamic plugin lookup will fail (duplicate critical issue).

The example plugin at examples/plugins/hello-world/main.go (lines 18-26) exports HTTPTransportMiddleware as a factory function:

func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware {
    return func(next fasthttp.RequestHandler) fasthttp.RequestHandler { ... }
}

However, line 145 attempts to cast the symbol directly to schemas.BifrostHTTPMiddleware, which will always fail because the symbol is a function that returns the middleware, not the middleware itself.

🔎 Proposed fix
 	// Looking up for HTTPTransportMiddleware method
 	httpTransportMiddlewareSym, err := plugin.Lookup("HTTPTransportMiddleware")
 	if err != nil {
 		return nil, err
 	}
-	if dp.httpTransportMiddleware, ok = httpTransportMiddlewareSym.(schemas.BifrostHTTPMiddleware); !ok {
-		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func(next fasthttp.RequestHandler) fasthttp.RequestHandler")
+	httpTransportMiddlewareFunc, ok := httpTransportMiddlewareSym.(func() schemas.BifrostHTTPMiddleware)
+	if !ok {
+		return nil, fmt.Errorf("failed to cast HTTPTransportMiddleware to func() schemas.BifrostHTTPMiddleware")
 	}
+	dp.httpTransportMiddleware = httpTransportMiddlewareFunc()
+	if dp.httpTransportMiddleware == nil {
+		return nil, fmt.Errorf("HTTPTransportMiddleware factory returned nil")
+	}
transports/bifrost-http/server/server.go (1)

950-1018: Use GetGovernancePluginName() when deciding whether to register governance routes.

RegisterAPIRoutes currently does:

governancePlugin, _ := FindPluginByName[schemas.Plugin](s.Plugins, governance.PluginName)

This bypasses OSSToEnterprisePluginNameOverrides and the GetGovernancePluginName() helper you already use in getGovernancePlugin(). In an enterprise deployment where the governance plugin is renamed, the plugin will be loaded, but the API routes won’t be registered because this lookup uses the hard‑coded OSS name.

Switch the lookup to:

governancePlugin, _ := FindPluginByName[schemas.Plugin](s.Plugins, s.GetGovernancePluginName())

so routing honors the same override mechanism as the rest of the server.

transports/bifrost-http/handlers/middlewares.go (1)

191-279: TracingMiddleware must pass a context.Context to StartSpan, not *fasthttp.RequestCtx.

Here you call:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

but ctx is *fasthttp.RequestCtx. The tracer’s StartSpan API is defined over context.Context, and prior work in this repo already introduced lib.ConvertToBifrostContext for this exact adaptation.

You should:

  • Convert the fasthttp context to a context.Context (e.g. bctx := lib.ConvertToBifrostContext(ctx)), and
  • Pass that into StartSpan, then use the returned spanCtx for any subsequent context‑based operations.

This is currently a type/semantic mismatch and will either fail to compile or break trace context propagation.

core/bifrost.go (1)

3893-3898: Critical: Fix tracer type assertion in Shutdown (regression or incomplete fix).

The type assertion at line 3895 is incorrect and will always fail. bifrost.tracer.Load() returns a *tracerWrapper, not a schemas.Tracer, so the assertion will never succeed and t.Stop() is never called. This causes background goroutines in the tracer to leak.

🔎 Proposed fix
 	// Stop the tracer to clean up background goroutines
-	if tracer := bifrost.tracer.Load(); tracer != nil {
-		if t, ok := tracer.(schemas.Tracer); ok {
-			t.Stop()
-		}
-	}
+	if v := bifrost.tracer.Load(); v != nil {
+		if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+			tw.tracer.Stop()
+		}
+	}

Note: A past review comment indicated this was addressed in commit 4d9d0fc, but the incorrect type assertion remains in the current code. Please verify whether this is a regression or if the fix was not fully applied.

🧹 Nitpick comments (7)
framework/streaming/chat.go (1)

164-178: LGTM! AccumulatedData initialization is complete.

All fields are explicitly initialized including the new TimeToFirstToken field. The initialization pattern is consistent with the audio streaming implementation, which promotes maintainability across the codebase.

Optional observation: The TTFT calculation logic (lines 157-161) is duplicated across multiple streaming files (audio.go, chat.go, and likely responses.go and transcription.go based on the PR context). Consider extracting this into a shared helper function to reduce duplication, though the current implementation is clear and correct.

🔎 Optional refactor to reduce duplication

You could add a helper method to the Accumulator or StreamAccumulator:

// calculateTTFT calculates Time To First Token in milliseconds
func (a *StreamAccumulator) calculateTTFT() int64 {
	if a.StartTimestamp.IsZero() || a.FirstChunkTimestamp.IsZero() {
		return 0
	}
	return a.FirstChunkTimestamp.Sub(a.StartTimestamp).Nanoseconds() / 1e6
}

Then use it in all streaming files:

-	// Calculate Time to First Token (TTFT) in milliseconds
-	var ttft int64
-	if !accumulator.StartTimestamp.IsZero() && !accumulator.FirstChunkTimestamp.IsZero() {
-		ttft = accumulator.FirstChunkTimestamp.Sub(accumulator.StartTimestamp).Nanoseconds() / 1e6
-	}
+	ttft := accumulator.calculateTTFT()
transports/bifrost-http/lib/config_test.go (1)

6675-12270: LoadConfig callsites correctly updated to new 2‑arg signature

All the updated invocations of LoadConfig in the SQLite integration tests now match func LoadConfig(ctx context.Context, configDirPath string) (*Config, error), and the surrounding usage of ctx := context.Background() and tempDir is unchanged, so behavior should be identical aside from dropping the unused third parameter. If you find yourself touching this area again, you could optionally DRY up the repeated ctx/tempDir → LoadConfig pattern with a small helper, but it isn’t required for this change.

plugins/otel/converter.go (1)

165-170: Potential integer overflow when converting uint64 to int64.

Converting uint64 to int64 can overflow for values greater than math.MaxInt64, resulting in negative values being reported in traces.

🔎 Suggested fix: clamp large uint64 values
 	case uint64:
-		return kvInt(key, int64(v))
+		if v > math.MaxInt64 {
+			return kvInt(key, math.MaxInt64)
+		}
+		return kvInt(key, int64(v))

You'll need to import math if not already imported.

transports/bifrost-http/handlers/middlewares.go (1)

49-68: Plugin HTTP transport middleware chaining is correct; consider caching the chain.

TransportInterceptorMiddleware now:

  • Pulls the current plugin snapshot via config.GetLoadedPlugins().
  • Collects non‑nil plugin.HTTPTransportMiddleware() functions.
  • Chains them via lib.ChainMiddlewares.

This matches the new HTTP middleware model and preserves ordering. If this ever shows up in profiles, you could later cache the assembled plugin chain and rebuild it only when the loaded plugin set changes, instead of per‑request.

framework/tracing/store.go (1)

13-371: TraceStore design (TTL cleanup, pooling, deferred spans) looks solid; only minor polish possible.

The TraceStore implementation correctly:

  • Uses sync.Map + pools for traces/spans.
  • Manages TTL‑based cleanup in a background goroutine with a guarded Stop() (stopOnce).
  • Provides simple APIs for deferred spans and streaming accumulation with proper locking.
  • Returns traces/spans to pools via ReleaseTrace / releaseSpan.

The only minor nit is that DeferredSpanInfo.Tracer and RequestID are currently unused; if they’re not needed by the tracer, consider removing them or wiring them up in future work.

core/bifrost.go (2)

2860-2871: Consider chaining context.WithValue calls for readability.

The current pattern of sequential context pointer updates works correctly but could be more idiomatic:

🔎 Suggested refactor
-	// Update context with span ID
-	*ctx = spanCtx
-
 	// Store tracer in context BEFORE calling requestHandler, so streaming goroutines
 	// have access to it for completing deferred spans when the stream ends.
 	// The streaming goroutine captures the context when it starts, so these values
 	// must be set before requestHandler() is called.
-	*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyTracer, tracer)
-
-	// Record stream start time for TTFT calculation (only for streaming requests)
-	// This is also used by RunPostHooks to detect streaming mode
-	if IsStreamRequestType(requestType) {
-		streamStartTime := time.Now()
-		*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyStreamStartTime, streamStartTime)
-	}
+	*ctx = spanCtx
+	*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyTracer, tracer)
+	if IsStreamRequestType(requestType) {
+		*ctx = context.WithValue(*ctx, schemas.BifrostContextKeyStreamStartTime, time.Now())
+	}

This reduces temporary variable allocations and keeps the context updates together.


3287-3293: Consider extracting span context propagation helper.

The pattern of propagating span context to pluginCtx is repeated in both RunPreHooks and RunPostHooks (non-streaming path):

if spanCtx != nil {
    if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
        pluginCtx.SetValue(schemas.BifrostContextKeySpanID, spanID)
    }
}
🔎 Suggested refactor

Extract to a helper method:

// propagateSpanContext copies span ID from source context to target context
func propagateSpanContext(sourceCtx context.Context, targetCtx *schemas.BifrostContext) {
    if sourceCtx == nil {
        return
    }
    if spanID, ok := sourceCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
        targetCtx.SetValue(schemas.BifrostContextKeySpanID, spanID)
    }
}

Then use:

propagateSpanContext(spanCtx, pluginCtx)

This reduces duplication and makes the intent clearer.

Also applies to: 3356-3362

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4d9d0fc and 810ac1d.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (91)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (3)
  • framework/configstore/tables/mcp.go
  • core/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (2)
  • plugins/maxim/version
  • plugins/governance/version
🚧 Files skipped from review as they are similar to previous changes (38)
  • framework/streaming/responses.go
  • core/bifrost_test.go
  • docs/docs.json
  • transports/bifrost-http/handlers/providers.go
  • transports/version
  • plugins/semanticcache/version
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/version
  • plugins/telemetry/main.go
  • plugins/maxim/main.go
  • plugins/logging/utils.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/health.go
  • framework/streaming/types.go
  • .github/workflows/scripts/release-bifrost-http.sh
  • plugins/otel/version
  • transports/bifrost-http/handlers/governance.go
  • core/schemas/bifrost.go
  • docs/plugins/migration-guide.mdx
  • framework/tracing/propagation.go
  • plugins/logging/version
  • transports/bifrost-http/handlers/mcpserver.go
  • framework/streaming/transcription.go
  • framework/streaming/accumulator.go
  • ui/components/ui/switch.tsx
  • ui/components/ui/sheet.tsx
  • plugins/jsonparser/version
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/version
  • framework/changelog.md
  • plugins/telemetry/version
  • framework/tracing/tracer.go
  • examples/plugins/hello-world/go.mod
  • docs/plugins/getting-started.mdx
  • plugins/governance/go.mod
  • docs/plugins/writing-plugin.mdx
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/maxim/changelog.md
  • core/schemas/plugin.go
  • plugins/jsonparser/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • plugins/semanticcache/changelog.md
  • plugins/otel/changelog.md
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/ui.go
  • examples/plugins/hello-world/main.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • framework/streaming/chat.go
  • framework/tracing/store.go
  • plugins/governance/changelog.md
  • transports/bifrost-http/integrations/router.go
  • plugins/otel/main.go
  • ui/components/sidebar.tsx
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/governance/main.go
  • transports/bifrost-http/server/server.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/config_test.go
  • core/version
  • core/changelog.md
  • plugins/semanticcache/main.go
  • transports/bifrost-http/handlers/config.go
  • plugins/otel/converter.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/integrations.go
  • plugins/mocker/changelog.md
  • core/schemas/tracer.go
  • framework/plugins/dynamicplugin.go
  • core/schemas/context.go
  • core/providers/utils/utils.go
  • plugins/logging/changelog.md
  • core/bifrost.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/tracing/llmspan.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/lib/middleware.go
  • core/schemas/trace.go
  • transports/changelog.md
  • plugins/jsonparser/main.go
  • plugins/telemetry/changelog.md
🧠 Learnings (4)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/ui.go
  • examples/plugins/hello-world/main.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • framework/streaming/chat.go
  • framework/tracing/store.go
  • transports/bifrost-http/integrations/router.go
  • plugins/otel/main.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/governance/main.go
  • transports/bifrost-http/server/server.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/config_test.go
  • plugins/semanticcache/main.go
  • transports/bifrost-http/handlers/config.go
  • plugins/otel/converter.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/integrations.go
  • core/schemas/tracer.go
  • framework/plugins/dynamicplugin.go
  • core/schemas/context.go
  • core/providers/utils/utils.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/tracing/llmspan.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/lib/middleware.go
  • core/schemas/trace.go
  • plugins/jsonparser/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (25)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/logging/main.go (6)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/bifrost.go (4)
  • RequestType (89-89)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyTraceID (143-143)
  • TranscriptionRequest (102-102)
ui/lib/types/config.ts (1)
  • RequestType (138-160)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/streaming/types.go (1)
  • ProcessedStreamResponse (118-126)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/store.go (3)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
plugins/otel/main.go (3)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/trace.go (1)
  • Trace (10-19)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
framework/streaming/audio.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/server/server.go (5)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • Plugin (71-97)
  • ObservabilityPlugin (123-137)
plugins/governance/main.go (2)
  • PluginName (23-23)
  • Config (34-36)
plugins/logging/main.go (2)
  • PluginName (22-22)
  • Config (92-94)
plugins/telemetry/main.go (2)
  • PluginName (21-21)
  • Config (64-66)
transports/bifrost-http/lib/config.go (2)
  • Config (189-220)
  • LoadConfig (276-356)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/config_test.go (1)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (276-356)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
core/schemas/context.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/providers/utils/utils.go (3)
core/schemas/bifrost.go (6)
  • BifrostResponse (335-355)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyStreamEndIndicator (131-131)
  • BifrostError (474-483)
  • BifrostContextKeyPostHookSpanFinalizer (149-149)
  • BifrostContextKeySpanID (144-144)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (161-161)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
transports/bifrost-http/lib/config.go (1)

1509-1535: Avoid nil dereference when FrameworkConfig.PricingSyncInterval is omitted

In initFrameworkConfigFromFile, when config.ConfigStore == nil and configData.FrameworkConfig.Pricing is set, you unconditionally dereference configData.FrameworkConfig.Pricing.PricingSyncInterval:

syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
pricingConfig.PricingSyncInterval = &syncDuration

If the JSON includes pricing with only pricing_url (no pricing_sync_interval), this will panic during startup.

Guard the pointer and optionally fall back to a default:

Suggested nil-safe handling
} else if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
-   pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
-   syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
-   pricingConfig.PricingSyncInterval = &syncDuration
+   pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
+   if configData.FrameworkConfig.Pricing.PricingSyncInterval != nil {
+     syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
+     pricingConfig.PricingSyncInterval = &syncDuration
+   }
+   // Optionally: else leave nil so modelcatalog.Init can apply its own default.
 }
♻️ Duplicate comments (17)
ui/components/ui/sheet.tsx (2)

103-103: Past review suggestion still not applied - verify if intentional.

The className logic still differs from the fix suggested in the past review (marked as addressed in commit 80eb07d). The current implementation always applies mb-6, whereas the suggested fix would only apply it when expandable is false:

  • Current: cn("mb-6 flex items-center", sheetContext?.expandable ? "p-0" : "")
    mb-6 is always present
  • Suggested: cn("flex items-center", sheetContext?.expandable ? "p-0" : "mb-6")
    mb-6 only when not expandable

If the current behavior is intentional (wanting margin-bottom even when expandable), please add a brief comment explaining the decision so future reviewers understand this was deliberate. Otherwise, apply the originally suggested fix.

🔎 Apply the originally suggested fix
-		<div data-slot="sheet-header" className={cn("mb-6 flex items-center", sheetContext?.expandable ? "p-0" : "")} {...props}>
+		<div data-slot="sheet-header" className={cn("flex items-center", sheetContext?.expandable ? "p-0" : "mb-6")} {...props}>

114-114: Past review suggestion still not applied - make ml-3 conditional.

The ml-3 class is applied unconditionally, but the expand/collapse button only renders when expandable is true AND side is "right". When the button is absent, this creates unwanted left margin.

🔎 Suggested fix
-			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center ml-3", className)}>{children}</div>
+			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center", sheetContext?.expandable && sheetContext?.side === "right" ? "ml-3" : "", className)}>{children}</div>
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

183-187: Add aria-label for accessibility.

The icon-only button lacks an accessible name for screen reader users. Include an aria-label to describe the button's purpose.

🔎 Suggested fix
 							<DropdownMenuTrigger asChild>
-								<Button variant="ghost" size="icon">
+								<Button variant="ghost" size="icon" aria-label="Log actions">
 									<MoreVertical className="h-3 w-3" />
 								</Button>
 							</DropdownMenuTrigger>
ui/components/ui/switch.tsx (1)

13-13: Breaking changes already comprehensively flagged in previous review.

The previous review comments thoroughly identified and analyzed the breaking changes introduced by:

  1. Changing default size from "default" to "md" (affects all Switch instances without explicit size prop)
  2. Changing border radius from rounded-full to rounded-sm (affects visual appearance of all switches)

Both changes impact existing UI across the codebase and should be carefully considered. The previous feedback includes detailed impact analysis and fix suggestions.

If these changes are intentional as part of a design system update, please:

  • Document the rationale in the PR description
  • Update visual regression tests or screenshots
  • Consider providing a migration guide or codemod for consuming code

Also applies to: 16-16, 26-26

framework/tracing/llmspan.go (2)

190-193: Namespace ExtraParams to avoid collisions with standard tracing attributes

Across the helpers you write ExtraParams directly into attrs with raw keys:

for k, v := range req.Params.ExtraParams {
    attrs[k] = fmt.Sprintf("%v", v)
}

This lets user-supplied keys overwrite core attributes like gen_ai.request.model or any future standard keys.

Consider namespacing and/or collision‑checking, e.g.:

Example namespacing approach
- for k, v := range req.Params.ExtraParams {
-     attrs[k] = fmt.Sprintf("%v", v)
- }
+ const extraPrefix = "gen_ai.extra."
+ for k, v := range req.Params.ExtraParams {
+     key := extraPrefix + k
+     // Only set if not already present to avoid clobbering core attributes
+     if _, exists := attrs[key]; !exists {
+         attrs[key] = fmt.Sprintf("%v", v)
+     }
+ }

Apply the same pattern to all the ExtraParams loops in this file.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029


377-381: Use float-safe formatting for embedding values instead of %d

PopulateEmbeddingRequestAttributes stringifies embedding elements with an integer verb:

embedding[i] = fmt.Sprintf("%d", v)

If Embedding is a float slice (typical for embeddings), this produces %!d(float64=…) strings and makes the attribute hard to consume.

Prefer a float-aware conversion:

Suggested change
-    } else if req.Input.Embedding != nil {
-        embedding := make([]string, len(req.Input.Embedding))
-        for i, v := range req.Input.Embedding {
-            embedding[i] = fmt.Sprintf("%d", v)
-        }
-        attrs[schemas.AttrInputEmbedding] = strings.Join(embedding, ",")
-    }
+    } else if req.Input.Embedding != nil {
+        embedding := make([]string, len(req.Input.Embedding))
+        for i, v := range req.Input.Embedding {
+            // Adjust precision/bitSize to match the actual element type if needed.
+            embedding[i] = fmt.Sprintf("%g", v)
+        }
+        attrs[schemas.AttrInputEmbedding] = strings.Join(embedding, ",")
+    }
transports/bifrost-http/integrations/router.go (1)

502-693: Critical: Non-streaming handlers are using the wrong context for Bifrost calls.

All non-streaming calls (lines 514, 534, 557, 580, 603, 626, 660) pass ctx (*fasthttp.RequestCtx) to the Bifrost client instead of the converted bifrostCtx:

listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
// ... etc

The comment at line 503 says "Use the cancellable context from ConvertToBifrostContext", and you already computed bifrostCtx earlier. Passing the HTTP request context here instead of the enriched Bifrost context:

  • Breaks tracing and plugin context propagation (all BifrostContextKey* values live on bifrostCtx)
  • May not compile if Bifrost methods expect context.Context rather than *fasthttp.RequestCtx

These calls should use the converted/cancellable context (*bifrostCtx), just like the streaming path does.

🔎 Proposed fix
 func (g *GenericRouter) handleNonStreamingRequest(ctx *fasthttp.RequestCtx, config RouteConfig, req interface{}, bifrostReq *schemas.BifrostRequest, bifrostCtx *context.Context) {
 	// Use the cancellable context from ConvertToBifrostContext
 	
 	var response interface{}
 	var err error
 
 	switch {
 	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)
 		// ...
 	case bifrostReq.TextCompletionRequest != nil:
-		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(*bifrostCtx, bifrostReq.TextCompletionRequest)
 		// ...
 	case bifrostReq.ChatRequest != nil:
-		chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+		chatResponse, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq.ChatRequest)
 		// ...
 	case bifrostReq.ResponsesRequest != nil:
-		responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+		responsesResponse, bifrostErr := g.client.ResponsesRequest(*bifrostCtx, bifrostReq.ResponsesRequest)
 		// ...
 	case bifrostReq.EmbeddingRequest != nil:
-		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(*bifrostCtx, bifrostReq.EmbeddingRequest)
 		// ...
 	case bifrostReq.SpeechRequest != nil:
-		speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+		speechResponse, bifrostErr := g.client.SpeechRequest(*bifrostCtx, bifrostReq.SpeechRequest)
 		// ...
 	case bifrostReq.TranscriptionRequest != nil:
-		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(*bifrostCtx, bifrostReq.TranscriptionRequest)

Please rebuild and test non-streaming requests (chat, embeddings, etc.) after fixing to verify tracing, plugin hooks, and timeouts work correctly.

transports/changelog.md (2)

16-23: Version references are inconsistent with core changelog.

The migration summary states v1.3.x (removed) and v1.4.x+ (new), but according to the core changelog (core/changelog.md), the new API is available in core v1.3.0+. This inconsistency could confuse developers about which versions to target.

Please align with core changelog:

  • Change "v1.4.x+ (new)" to "v1.3.0+ (new)"
  • Adjust "v1.3.x (removed)" to "v1.2.x (removed)" to match the version where the old API existed

16-16: Add language specifier to fenced code block.

The fenced code block is missing a language identifier, which prevents proper syntax highlighting.

🔎 Suggested fix
   **Migration summary:**
-  ```
+  ```text
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)
core/providers/utils/utils.go (1)

1428-1432: Refactor to pass context by value, not by pointer.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. Contexts should always be passed by value, never by pointer. The context.Context type is designed to be passed by value and is safe for concurrent use.

🔎 Proposed fix
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == context.Background() || ctx == context.TODO() {
 		return
 	}
 
-	// Get the trace ID from context (this IS available in the provider's goroutine)
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
 	if !ok || traceID == "" {
 		return
 	}
 
-	// Get the tracer from context
-	tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+	tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
 	if tracerVal == nil {
 		return
 	}

And update all call sites:

-			completeDeferredSpan(&ctx, response, nil)
+			completeDeferredSpan(ctx, response, nil)
plugins/governance/main.go (2)

233-256: Validate x-bf-vk prefix and add x-goog-api-key support in parseVirtualKey

parseVirtualKey still:

  • Accepts any non‑empty x-bf-vk without validating the VirtualKeyPrefix, unlike Authorization and x-api-key.
  • Ignores x-goog-api-key, even though the rest of the stack treats it as another virtual-key carrier.

This leads to inconsistent enforcement and allows malformed x-bf-vk values to hit GetVirtualKey, and also means governance/MCP headers won’t be applied for requests authenticating solely via x-goog-api-key.

Consider:

  • Adding the same VirtualKeyPrefix check for x-bf-vk (trim + lower-case, then HasPrefix).
  • Adding a x-goog-api-key branch mirroring the x-api-key logic, so all supported VK headers are treated consistently.
Example adjustment
 func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
-	var virtualKeyValue string
-	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
-	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
-	}
+	var virtualKeyValue string
+	if raw := string(ctx.Request.Header.Peek("x-bf-vk")); raw != "" {
+		v := strings.TrimSpace(raw)
+		if strings.HasPrefix(strings.ToLower(v), VirtualKeyPrefix) {
+			return bifrost.Ptr(v)
+		}
+	}
 	// ... existing Authorization handling ...
-	xAPIKey := string(ctx.Request.Header.Peek("x-api-key"))
-	if xAPIKey != "" && strings.HasPrefix(strings.ToLower(xAPIKey), VirtualKeyPrefix) {
-		return bifrost.Ptr(xAPIKey)
-	}
+	if v := string(ctx.Request.Header.Peek("x-api-key")); v != "" &&
+		strings.HasPrefix(strings.ToLower(v), VirtualKeyPrefix) {
+		return bifrost.Ptr(v)
+	}
+	if v := string(ctx.Request.Header.Peek("x-goog-api-key")); v != "" &&
+		strings.HasPrefix(strings.ToLower(v), VirtualKeyPrefix) {
+		return bifrost.Ptr(v)
+	}
 	return nil
 }
#!/bin/bash
# Verify all virtual-key sources are handled consistently
rg -n "BifrostContextKeyVirtualKey|x-bf-vk|x-api-key|x-goog-api-key" -S

258-306: Fix log message: Unmarshal vs Marshal in middleware body handling

In HTTPTransportMiddleware:

  • Line 287: sonic.Unmarshal is used, but the error log says "failed to marshal request body...".
  • Line 301: sonic.Marshal uses the same message, which is accurate there.

To avoid confusion when debugging, update the first log message to say “unmarshal”:

Suggested tweak
-	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
-	if err != nil {
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+	if err != nil {
+		p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)
transports/bifrost-http/handlers/middlewares.go (1)

201-215: TracingMiddleware.Middleware must pass a context.Context to StartSpan, not *fasthttp.RequestCtx

Here:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

ctx is *fasthttp.RequestCtx, but StartSpan is defined on the tracer as:

StartSpan(ctx context.Context, name string, kind schemas.SpanKind) (context.Context, schemas.SpanHandle)

*fasthttp.RequestCtx does not implement context.Context, so this will not compile and, even if coerced, would break the tracer’s expectations around Value/deadline propagation. This is essentially the same issue previously flagged and marked as addressed.

You should convert the HTTP context into a context.Context first (e.g. via your existing lib.ConvertToBifrostContext) and pass that into StartSpan, then propagate the resulting span ID back into the fasthttp context:

Suggested fix sketch
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Create a Bifrost/context.Context from the fasthttp context
+			bctx := lib.ConvertToBifrostContext(ctx)
+			// Create root span for the HTTP request
+			spanCtx, rootSpan := m.tracer.Load().StartSpan(bctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
 			if rootSpan != nil {
 				m.tracer.Load().SetAttribute(rootSpan, "http.method", string(ctx.Method()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.url", string(ctx.RequestURI()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.user_agent", string(ctx.Request.Header.UserAgent()))
-				// Set root span ID in context for child span creation
-				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
-					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
-				}
+				// Set root span ID in the fasthttp context for child span creation
+				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
+					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
+				}
 			}

(Adapt lib.ConvertToBifrostContext usage to match its actual signature.)

This keeps the tracer API correctly fed with a real context.Context while still exposing the span ID through RequestCtx.UserValue.

#!/bin/bash
# Show StartSpan signature and its usages to confirm the type mismatch
rg -n "StartSpan\(ctx context.Context" core/schemas/tracer.go -n
rg -n "StartSpan\(" transports/bifrost-http/handlers/middlewares.go -n -C2

Also applies to: 217-225, 227-279

core/schemas/trace.go (3)

41-49: Trace.Reset() lacks mutex protection.

This method modifies all fields including Spans without holding the mutex, creating a potential data race if Reset is called while another goroutine is accessing the trace (e.g., via AddSpan or GetSpan). Add mutex protection or document that Reset must only be called when exclusive access is guaranteed.

🔎 Proposed fix
 // Reset clears the trace for reuse from pool
 func (t *Trace) Reset() {
+	t.mu.Lock()
+	defer t.mu.Unlock()
 	t.TraceID = ""
 	t.ParentID = ""
 	t.RootSpan = nil
 	t.Spans = t.Spans[:0]
 	t.StartTime = time.Time{}
 	t.EndTime = time.Time{}
 	t.Attributes = nil
 }

85-89: Span.End() method lacks mutex protection.

Unlike SetAttribute and AddEvent, the End method modifies EndTime, Status, and StatusMsg without holding the mutex. If End is called concurrently with reads or writes to these fields, a data race may occur.

🔎 Proposed fix
 // End marks the span as complete with the given status
 func (s *Span) End(status SpanStatus, statusMsg string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.EndTime = time.Now()
 	s.Status = status
 	s.StatusMsg = statusMsg
 }

92-104: Span.Reset() lacks mutex protection.

Similar to Trace.Reset(), this method modifies fields without holding the mutex. If reused from a pool while another goroutine still holds a reference, a data race could occur.

🔎 Proposed fix
 // Reset clears the span for reuse from pool
 func (s *Span) Reset() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.SpanID = ""
 	s.ParentID = ""
 	s.TraceID = ""
 	s.Name = ""
 	s.Kind = SpanKindUnspecified
 	s.StartTime = time.Time{}
 	s.EndTime = time.Time{}
 	s.Status = SpanStatusUnset
 	s.StatusMsg = ""
 	s.Attributes = nil
 	s.Events = s.Events[:0]
 }
framework/tracing/tracer.go (1)

394-440: Missing sort calls for deterministic ordering in buildCompleteResponseFromChunks.

The comments at lines 395 and 434 claim to "Sort choice indices for deterministic output" and "Sort tool calls by index", but the actual sort.Ints() calls are missing. Without sorting, the order of choices and tool calls will be nondeterministic because map iteration order is randomized in Go.

Add sort.Ints(choiceIndices) after line 398 and sort.Ints(tcIndices) after line 437 to ensure deterministic, reproducible output.

🔎 Proposed fix

Add the sort import if not already present:

import (
	"context"
+	"sort"
	"time"
	
	"github.com/maximhq/bifrost/core/schemas"
	"github.com/maximhq/bifrost/framework/modelcatalog"
	"github.com/maximhq/bifrost/framework/streaming"
)

Then add the sorting calls:

 	// Build final choices from accumulated data
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)

 	for _, idx := range choiceIndices {
 		accum := choiceMap[idx]
		...
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
🧹 Nitpick comments (5)
framework/tracing/propagation.go (1)

10-29: Treat all‑zero trace/span IDs as invalid to fully align with W3C Trace Context

normalizeTraceID, normalizeSpanID, and ParseTraceparent validate hex and length but accept all‑zero IDs, which the W3C Trace Context spec marks as invalid for both trace-id and parent-id. That can yield headers some tracers will reject.

Consider rejecting all‑zero IDs during normalization and parsing so invalid upstream values don’t silently propagate:

Suggested guard for all‑zero IDs
 func normalizeTraceID(traceID string) string {
   normalized := strings.ReplaceAll(traceID, "-", "")
   normalized = strings.ToLower(normalized)

   if len(normalized) != 32 {
     return ""
   }
-  if !isHex(normalized) {
+  if !isHex(normalized) || normalized == strings.Repeat("0", 32) {
     return ""
   }
   return normalized
 }

 func normalizeSpanID(spanID string) string {
   normalized := strings.ReplaceAll(spanID, "-", "")
   normalized = strings.ToLower(normalized)

   if len(normalized) > 16 {
     normalized = normalized[:16]
   }
   if len(normalized) != 16 {
     return ""
   }
-  if !isHex(normalized) {
+  if !isHex(normalized) || normalized == strings.Repeat("0", 16) {
     return ""
   }
   return normalized
 }

 func ParseTraceparent(traceparent string) *W3CTraceContext {
   parts := strings.Split(traceparent, "-")
   if len(parts) != 4 {
     return nil
   }
   version, traceID, parentID, traceFlags := parts[0], parts[1], parts[2], parts[3]

   if version != "00" {
     return nil
   }
-  if len(traceID) != 32 || !isHex(traceID) {
+  if len(traceID) != 32 || !isHex(traceID) || traceID == strings.Repeat("0", 32) {
     return nil
   }
-  if len(parentID) != 16 || !isHex(parentID) {
+  if len(parentID) != 16 || !isHex(parentID) || parentID == strings.Repeat("0", 16) {
     return nil
   }
   if len(traceFlags) != 2 || !isHex(traceFlags) {
     return nil
   }
   ...
 }

Also applies to: 31-56, 123-136

core/bifrost.go (3)

2350-2379: Fallback span instrumentation is correct but duplicated

The new fallback spans in both handleRequest and handleStreamRequest correctly capture provider/model/index and outcome (ok vs error) on SpanKindFallback spans. The logic is essentially duplicated between the two methods though; consider a small helper (e.g. withFallbackSpan(...)) to reduce drift if you tweak attributes or naming later.

Also applies to: 2449-2477


3322-3384: Streaming post‑hook aggregation works, but comment about pre‑hook hierarchy is slightly misleading

The streaming path in RunPostHooks correctly switches from per‑plugin spans to timing accumulation, and FinalizeStreamingPostHookSpans emits one aggregated plugin.<name>.posthook span per plugin with invocation/error counts and avg/total durations. However:

  • postHookPluginOrder is built in post‑hook execution order (reverse of pre‑hook order), so the nested spans reflect the post‑hook call stack, not strictly the pre‑hook hierarchy as the comment suggests.

This is mostly a documentation nit, but if you really want “pre‑hook hierarchy” semantics, you’d need to either record pre‑hook order separately or reverse postHookPluginOrder when building aggregated spans.

Also applies to: 3399-3410, 3412-3484


3893-3896: Optional: guard Shutdown tracer access defensively

Shutdown currently does bifrost.tracer.Load().(*tracerWrapper) without checking for a nil value. Under normal construction via Init this is fine, but a zero‑value Bifrost or misuse of SetTracer in tests would panic here.

Consider a small guard:

Suggested defensive tweak
-	if tracerWrapper := bifrost.tracer.Load().(*tracerWrapper); tracerWrapper != nil && tracerWrapper.tracer != nil {
-		tracerWrapper.tracer.Stop()
-	}
+	if v := bifrost.tracer.Load(); v != nil {
+		if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+			tw.tracer.Stop()
+		}
+	}
framework/tracing/store.go (1)

197-242: Consider documenting the span hierarchy behavior.

StartSpan always sets the parent to the root span if one exists (lines 231-236), meaning it can only create the root span or direct children of the root. For deeper span hierarchies, callers must use StartChildSpan. This design is likely intentional to keep trace-level span creation simple, but it would be clearer with a brief comment explaining the hierarchy constraint.

📝 Suggested documentation addition
-// StartSpan creates a new span and adds it to the trace
+// StartSpan creates a new span and adds it to the trace.
+// If a root span exists, the new span becomes a child of the root.
+// For deeper span hierarchies, use StartChildSpan to specify a parent span ID.
 func (s *TraceStore) StartSpan(traceID, name string, kind schemas.SpanKind) *schemas.Span {
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 810ac1d and 330f7f3.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (91)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
💤 Files with no reviewable changes (3)
  • plugins/otel/ttlsyncmap.go
  • framework/configstore/tables/mcp.go
  • core/mcp.go
✅ Files skipped from review due to trivial changes (2)
  • plugins/otel/version
  • plugins/jsonparser/version
🚧 Files skipped from review as they are similar to previous changes (39)
  • .github/workflows/scripts/release-bifrost-http.sh
  • framework/streaming/audio.go
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • framework/streaming/types.go
  • ui/components/sidebar.tsx
  • plugins/telemetry/main.go
  • plugins/maxim/version
  • plugins/semanticcache/version
  • core/schemas/context.go
  • plugins/semanticcache/main.go
  • docs/plugins/migration-guide.mdx
  • framework/streaming/chat.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • plugins/logging/utils.go
  • plugins/mocker/version
  • plugins/semanticcache/changelog.md
  • transports/bifrost-http/lib/config_test.go
  • framework/tracing/helpers.go
  • plugins/mocker/changelog.md
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/inference.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/health.go
  • docs/plugins/writing-plugin.mdx
  • framework/streaming/transcription.go
  • core/version
  • transports/bifrost-http/handlers/governance.go
  • examples/plugins/hello-world/go.mod
  • core/bifrost_test.go
  • transports/bifrost-http/lib/middleware.go
  • plugins/maxim/main.go
  • plugins/maxim/changelog.md
  • plugins/jsonparser/main.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/version
  • examples/plugins/hello-world/main.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/websocket.go
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/logging.go
  • plugins/mocker/main.go
  • plugins/governance/changelog.md
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/integrations.go
  • plugins/jsonparser/changelog.md
  • plugins/logging/changelog.md
  • plugins/governance/version
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/integrations/router.go
  • plugins/governance/main.go
  • framework/tracing/propagation.go
  • plugins/telemetry/changelog.md
  • docs/plugins/getting-started.mdx
  • core/schemas/trace.go
  • framework/tracing/store.go
  • framework/streaming/accumulator.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/tracer.go
  • plugins/logging/main.go
  • transports/changelog.md
  • transports/bifrost-http/lib/config.go
  • plugins/otel/main.go
  • plugins/logging/version
  • transports/bifrost-http/handlers/mcpserver.go
  • ui/components/ui/sheet.tsx
  • plugins/otel/converter.go
  • docs/docs.json
  • plugins/governance/go.mod
  • plugins/otel/changelog.md
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
  • framework/plugins/dynamicplugin_test.go
  • core/changelog.md
  • ui/components/ui/switch.tsx
  • framework/tracing/tracer.go
  • framework/changelog.md
  • framework/tracing/llmspan.go
  • transports/version
  • core/bifrost.go
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/websocket.go
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/logging.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/integrations/router.go
  • plugins/governance/main.go
  • framework/tracing/propagation.go
  • core/schemas/trace.go
  • framework/tracing/store.go
  • framework/streaming/accumulator.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/tracer.go
  • plugins/logging/main.go
  • transports/bifrost-http/lib/config.go
  • plugins/otel/main.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/otel/converter.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
  • framework/plugins/dynamicplugin_test.go
  • framework/tracing/tracer.go
  • framework/tracing/llmspan.go
  • core/bifrost.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (25)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/middlewares.go (5)
core/schemas/plugin.go (2)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (161-161)
framework/tracing/store.go (3)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
core/providers/utils/utils.go (2)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/tracer.go (8)
core/schemas/bifrost.go (4)
  • ModelProvider (33-33)
  • BifrostError (474-483)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/schemas/context.go (1)
  • BifrostContext (32-42)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
plugins/otel/main.go (3)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
framework/tracing/tracer.go (10)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • SpanHandle (11-11)
  • StreamAccumulatorResult (15-33)
core/schemas/trace.go (4)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (6)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
  • BifrostContextKeyRequestID (124-124)
  • BifrostContextKeyStreamEndIndicator (131-131)
framework/tracing/llmspan.go (3)
  • PopulateRequestAttributes (14-60)
  • PopulateResponseAttributes (64-106)
  • PopulateErrorAttributes (109-124)
core/schemas/chatcompletions.go (1)
  • BifrostChatResponse (27-42)
core/schemas/models.go (1)
  • Model (109-129)
framework/streaming/types.go (1)
  • StreamResponseTypeFinal (24-24)
framework/tracing/llmspan.go (5)
core/schemas/bifrost.go (18)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • TextCompletionRequest (93-93)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • SpeechRequest (100-100)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (11)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrMaxTokens (163-163)
  • AttrTemperature (164-164)
  • AttrTopP (165-165)
  • AttrStopSequences (166-166)
  • AttrPresencePenalty (167-167)
  • AttrFrequencyPenalty (168-168)
  • AttrParallelToolCall (169-169)
  • AttrRequestUser (170-170)
  • AttrUsageCost (213-213)
core/schemas/chatcompletions.go (2)
  • BifrostChatRequest (12-19)
  • BifrostChatResponse (27-42)
core/schemas/files.go (5)
  • BifrostFileUploadRequest (53-67)
  • BifrostFileListRequest (109-128)
  • BifrostFileRetrieveRequest (146-159)
  • BifrostFileDeleteRequest (186-198)
  • BifrostFileContentRequest (215-227)
framework/modelcatalog/main.go (1)
  • ModelCatalog (23-46)
core/bifrost.go (5)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • DefaultTracer (181-183)
  • SpanHandle (11-11)
core/schemas/bifrost.go (9)
  • BifrostRequest (173-193)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyStreamStartTime (145-145)
  • BifrostContextKeyTraceID (143-143)
  • BifrostResponse (335-355)
  • RequestType (89-89)
  • BifrostContextKeyPostHookSpanFinalizer (149-149)
  • BifrostError (474-483)
  • BifrostContextKeySpanID (144-144)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/context.go (1)
  • NewBifrostContextWithTimeout (70-73)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch 2 times, most recently from 6fb3318 to 3f130fd Compare December 25, 2025 07:54
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

♻️ Duplicate comments (17)
plugins/jsonparser/version (1)

1-1: Patch version increment inconsistent with breaking changes.

This concern was already raised in a previous review. The changelog documents breaking changes (TransportInterceptor → HTTPTransportMiddleware), but the version increment is only a patch bump (1.3.52 → 1.3.53). Per semantic versioning, breaking changes warrant at minimum a minor version bump.

plugins/otel/changelog.md (1)

19-19: Verify the migration guide link exists.

The changelog references /docs/plugins/migration-guide at line 19. A previous review comment flagged that this file doesn't exist in the repository. Ensure the migration guide is created before merging, or update this link to point to the correct location.

#!/bin/bash
# Description: Verify the migration guide exists and check alternative paths

# Check if the exact path exists
if fd -t f "migration-guide" docs/; then
  echo "Migration guide found:"
  fd -t f "migration-guide" docs/
else
  echo "Migration guide not found at expected path"
fi

# Also check for plugin documentation structure
echo "Plugin docs structure:"
fd -t f . docs/plugins/ 2>/dev/null || echo "docs/plugins/ directory not found"
fd -t f . docs/features/plugins/ 2>/dev/null || echo "docs/features/plugins/ directory not found"
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

183-187: Add aria-label for screen reader accessibility.

The icon-only button at line 184 lacks an accessible name for screen reader users. Add an aria-label attribute to improve accessibility.

🔎 Proposed fix
 <DropdownMenuTrigger asChild>
-  <Button variant="ghost" size="icon">
+  <Button variant="ghost" size="icon" aria-label="Log actions">
     <MoreVertical className="h-3 w-3" />
   </Button>
 </DropdownMenuTrigger>
core/providers/utils/utils.go (1)

1424-1513: Refactor to pass context by value.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. The context.Context interface should always be passed by value, never by pointer, as it's designed for safe concurrent use and value semantics.

🔎 Proposed fix

Update the function signature and body:

-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
   if ctx == nil {
     return
   }
 
-  traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+  traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
   if !ok || traceID == "" {
     return
   }
 
-  tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+  tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
   // ... rest of function updated similarly

Update all call sites:

-completeDeferredSpan(&ctx, processedResponse, processedError)
+completeDeferredSpan(ctx, processedResponse, processedError)
framework/streaming/chat.go (1)

157-171: TTFT calculation is correct but duplicated.

The TTFT calculation logic is correct and properly guards against zero timestamps. However, this is identical to the implementation in framework/streaming/responses.go. See the refactoring suggestion in that file to extract this into a shared helper function.

transports/bifrost-http/integrations/router.go (1)

508-514: Non-streaming path passes wrong context to Bifrost client.

The non-streaming request handlers pass ctx (*fasthttp.RequestCtx) to Bifrost client methods (Lines 514, 534, 557, 580, 603, 626, 660), but the comment at Line 503-507 states "Use the cancellable context from ConvertToBifrostContext". The bifrostCtx contains trace propagation, plugin context keys, and timeout handling that would be lost.

This appears to be the same issue flagged in a previous review. Passing *bifrostCtx (dereferenced) instead of ctx would ensure tracing and plugin hooks work correctly.

🔎 Suggested fix
 case bifrostReq.ListModelsRequest != nil:
-	listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+	listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)

Apply similar changes to all other non-streaming request type cases (TextCompletionRequest, ChatCompletionRequest, ResponsesRequest, EmbeddingRequest, SpeechRequest, TranscriptionRequest).

transports/changelog.md (1)

16-23: Add language specifier to fenced code block.

The fenced code block lacks a language identifier, which prevents proper syntax highlighting. Add go or text after the opening backticks.

🔎 Suggested fix
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)
plugins/governance/main.go (1)

233-256: Missing prefix validation for x-bf-vk header.

The parseVirtualKey function validates that values from Authorization (Bearer) and x-api-key headers start with VirtualKeyPrefix, but the x-bf-vk header at lines 235-238 is returned without prefix validation. This inconsistency could allow invalid virtual keys to pass through.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
-		return bifrost.Ptr(string(vkHeader))
+			return bifrost.Ptr(vkValue)
+		}
 	}
framework/tracing/store.go (1)

13-22: Trim unused fields from DeferredSpanInfo and clear streaming state on TTL cleanup

  • DeferredSpanInfo.Tracer and DeferredSpanInfo.RequestID are never assigned or read; they just bloat each deferred entry. This was called out in an earlier review and still appears unused here.
  • cleanupOldTraces evicts expired traces from traces and releases them, but does not delete any corresponding entries in deferredSpans. For long‑running processes with abandoned streaming traces, this can leak accumulated chunk buffers.

Consider:

  • Dropping the unused fields from DeferredSpanInfo, or wiring them up if you really need them.
  • In cleanupOldTraces, deleting the deferred span entry for each removed trace (e.g. using trace.TraceID from the deleted value).
Example adjustments
 type DeferredSpanInfo struct {
 	SpanID             string
 	StartTime          time.Time
-	Tracer             schemas.Tracer // Reference to tracer for completing the span
-	RequestID          string         // Request ID for accumulator lookup
 	FirstChunkTime     time.Time      // Timestamp of first chunk (for TTFT calculation)
 	AccumulatedChunks  []*schemas.BifrostResponse // Accumulated streaming chunks
 	mu                 sync.Mutex     // Mutex for thread-safe chunk accumulation
@@
 func (s *TraceStore) cleanupOldTraces() {
 	cutoff := time.Now().Add(-s.ttl)
@@
-	s.traces.Range(func(key, value any) bool {
-		trace := value.(*schemas.Trace)
+	s.traces.Range(func(key, value any) bool {
+		trace := value.(*schemas.Trace)
 		if trace.StartTime.Before(cutoff) {
 			if deleted, ok := s.traces.LoadAndDelete(key); ok {
-				s.ReleaseTrace(deleted.(*schemas.Trace))
+				t := deleted.(*schemas.Trace)
+				// Drop any deferred streaming state for this trace
+				s.ClearDeferredSpan(t.TraceID)
+				s.ReleaseTrace(t)
 				count++
 			}

Also applies to: 119-177, 341-360

transports/bifrost-http/handlers/middlewares.go (1)

191-307: Fix TracingMiddleware to pass a context.Context into StartSpan instead of *fasthttp.RequestCtx

Tracer.StartSpan is defined against context.Context, but Middleware() currently calls:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

where ctx is *fasthttp.RequestCtx. This does not satisfy context.Context and will not compile; even if it did, the tracer implementation expects a real context.Context (it calls Value on it, etc.).

Use the existing adapter pattern (as in the inference handlers) to convert the fasthttp context to a Bifrost context first, and then pass that into StartSpan, while still propagating the resulting span ID back into the fasthttp RequestCtx:

Suggested fix using `lib.ConvertToBifrostContext`
 func (m *TracingMiddleware) Middleware() schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
@@
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Create root span for the HTTP request using a standard context
+			bfCtx := lib.ConvertToBifrostContext(ctx)
+			spanCtx, rootSpan := m.tracer.Load().StartSpan(bfCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
 			if rootSpan != nil {
 				m.tracer.Load().SetAttribute(rootSpan, "http.method", string(ctx.Method()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.url", string(ctx.RequestURI()))
 				m.tracer.Load().SetAttribute(rootSpan, "http.user_agent", string(ctx.Request.Header.UserAgent()))
 				// Set root span ID in context for child span creation
 				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
 					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
 				}
 			}

This keeps tracing consistent with the rest of the stack and avoids a type mismatch at compile time.

core/schemas/trace.go (3)

40-49: Trace.Reset() lacks mutex protection — potential data race.

This method modifies all fields including Spans without holding the mutex, creating a data race if Reset is called while another goroutine is accessing the trace (e.g., via AddSpan or GetSpan).

This was flagged in a previous review. Add mutex protection:

🔎 Proposed fix
 // Reset clears the trace for reuse from pool
 func (t *Trace) Reset() {
+	t.mu.Lock()
+	defer t.mu.Unlock()
 	t.TraceID = ""
 	t.ParentID = ""
 	t.RootSpan = nil
 	t.Spans = t.Spans[:0]
 	t.StartTime = time.Time{}
 	t.EndTime = time.Time{}
 	t.Attributes = nil
 }

84-89: Span.End() lacks mutex protection — potential data race.

Unlike SetAttribute and AddEvent, the End method modifies EndTime, Status, and StatusMsg without holding the mutex.

🔎 Proposed fix
 // End marks the span as complete with the given status
 func (s *Span) End(status SpanStatus, statusMsg string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.EndTime = time.Now()
 	s.Status = status
 	s.StatusMsg = statusMsg
 }

91-104: Span.Reset() lacks mutex protection — potential data race.

Similar to Trace.Reset(), this method modifies fields without holding the mutex.

🔎 Proposed fix
 // Reset clears the span for reuse from pool
 func (s *Span) Reset() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.SpanID = ""
 	s.ParentID = ""
 	// ... rest of fields
 }
framework/tracing/tracer.go (2)

393-398: Choice indices are not sorted despite the comment.

The comment at line 394 says "Sort choice indices for deterministic output" but no sorting is performed. The iteration order over a map in Go is non-deterministic, so the resulting choices may appear in different orders across runs.

This was flagged in a previous review and marked as addressed, but the sorting code is still missing.

🔎 Proposed fix
+import "sort"

 // Build final choices from accumulated data
 // Sort choice indices for deterministic output
 choiceIndices := make([]int, 0, len(choiceMap))
 for idx := range choiceMap {
     choiceIndices = append(choiceIndices, idx)
 }
+sort.Ints(choiceIndices)

433-441: Tool call indices are not sorted despite the comment.

Similarly, the comment at line 433 says "Sort tool calls by index" but no sorting is performed. The iteration order will be non-deterministic.

🔎 Proposed fix
 // Sort tool calls by index
 tcIndices := make([]int, 0, len(accum.toolCalls))
 for tcIdx := range accum.toolCalls {
     tcIndices = append(tcIndices, tcIdx)
 }
+sort.Ints(tcIndices)
 toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
framework/tracing/llmspan.go (2)

190-193: ExtraParams keys may collide with standard attribute names.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing. If a user sets a key like "gen_ai.request.model" in ExtraParams, it would overwrite the standard attribute set earlier.

This pattern repeats at lines 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029.

🔎 Suggested approach: namespace ExtraParams
 // ExtraParams
 for k, v := range req.Params.ExtraParams {
-    attrs[k] = fmt.Sprintf("%v", v)
+    attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 }

376-382: Use a float-appropriate formatter for embedding values.

The code uses %d format verb for embedding values:

embedding[i] = fmt.Sprintf("%d", v)

If Embedding is a float slice (typical for embeddings), %d is incorrect and will truncate values or produce unexpected results.

🔎 Proposed fix
 } else if req.Input.Embedding != nil {
     embedding := make([]string, len(req.Input.Embedding))
     for i, v := range req.Input.Embedding {
-        embedding[i] = fmt.Sprintf("%d", v)
+        embedding[i] = fmt.Sprintf("%g", v)
     }
     attrs[schemas.AttrInputEmbedding] = strings.Join(embedding, ",")
 }
🧹 Nitpick comments (8)
framework/streaming/responses.go (1)

742-756: Extract duplicated TTFT calculation logic.

The TTFT calculation logic here is identical to the implementation in framework/streaming/chat.go (lines 157-171). This violates the DRY principle and creates maintenance burden.

🔎 Proposed refactor to extract common TTFT calculation

Add a helper function in framework/streaming/types.go or framework/streaming/accumulator.go:

// CalculateTTFT calculates Time to First Token in milliseconds from start and first chunk timestamps.
// Returns 0 if either timestamp is zero.
func CalculateTTFT(startTimestamp, firstChunkTimestamp time.Time) int64 {
	if startTimestamp.IsZero() || firstChunkTimestamp.IsZero() {
		return 0
	}
	return firstChunkTimestamp.Sub(startTimestamp).Nanoseconds() / 1e6
}

Then update this file:

-	// Calculate Time to First Token (TTFT) in milliseconds
-	var ttft int64
-	if !accumulator.StartTimestamp.IsZero() && !accumulator.FirstChunkTimestamp.IsZero() {
-		ttft = accumulator.FirstChunkTimestamp.Sub(accumulator.StartTimestamp).Nanoseconds() / 1e6
-	}
+	// Calculate Time to First Token (TTFT) in milliseconds
+	ttft := CalculateTTFT(accumulator.StartTimestamp, accumulator.FirstChunkTimestamp)

	// Initialize accumulated data
	data := &AccumulatedData{

Apply the same change to framework/streaming/chat.go, framework/streaming/audio.go, and framework/streaming/transcription.go.

ui/components/devProfiler.tsx (2)

17-24: Handle edge cases in formatBytes for negative or extremely large values.

The function could produce unexpected results for edge cases: Math.log of a negative number returns NaN, and very large numbers could exceed the sizes array bounds.

🔎 Suggested improvement
 function formatBytes (bytes: number): string {
   if (bytes === 0) return '0 B'
+  if (bytes < 0) return `-${formatBytes(-bytes)}`
   const k = 1024
   const sizes = ['B', 'KB', 'MB', 'GB']
-  const i = Math.floor(Math.log(bytes) / Math.log(k))
+  const i = Math.min(Math.floor(Math.log(bytes) / Math.log(k)), sizes.length - 1)
   return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
 }

369-394: Consider using a stable key for allocation items.

Using array index as key is generally discouraged for dynamic lists, but since this list is refreshed as a whole from the API and doesn't support reordering/insertion, it's acceptable here. If allocations gain unique identifiers in the future, prefer using those.

framework/streaming/accumulator.go (1)

138-141: Consider extracting duplicated FirstChunkTimestamp logic.

The same FirstChunkTimestamp tracking logic is repeated across all four add*StreamChunk methods. While functional, this could be extracted into a helper method to reduce duplication.

🔎 Example helper extraction
// trackFirstChunk sets FirstChunkTimestamp if not already set.
// Must be called while holding accumulator.mu lock.
func (acc *StreamAccumulator) trackFirstChunk(chunkTimestamp time.Time) {
    if acc.FirstChunkTimestamp.IsZero() {
        acc.FirstChunkTimestamp = chunkTimestamp
    }
}

Then in each add method:

accumulator.trackFirstChunk(chunk.Timestamp)

Also applies to: 162-165, 186-189, 210-213

framework/tracing/propagation.go (1)

118-121: Consider allowing higher version parsing per W3C spec.

The W3C Trace Context spec recommends that implementations attempt to parse higher versions by extracting trace-id, parent-id, and the sampled bit, rather than rejecting them outright. However, strictly supporting only version "00" is a reasonable conservative choice for now.

core/bifrost.go (1)

70-71: Tighten tracer atomic access and fix stale field comment

  • The field comment on Line 70 says atomic.Value “stores schemas.Tracer”, but you now always store *tracerWrapper. This is misleading for future maintainers.
  • Both getTracer() and Shutdown() currently do an unchecked .(*tracerWrapper) on atomic.Value.Load(). If Bifrost were ever constructed without Init/SetTracer (tests, future code, or partial initialization), these would panic.

Consider:

  • Updating the comment to reflect *tracerWrapper.
  • Making getTracer and Shutdown defensively handle a nil/incorrect value and fall back to schemas.DefaultTracer() or simply no-op in Shutdown.
Example defensive tweak
-	tracer              atomic.Value                       // tracer for distributed tracing (stores schemas.Tracer, NoOpTracer if not configured)
+	tracer              atomic.Value                       // stores *tracerWrapper wrapping a schemas.Tracer (NoOpTracer if not configured)

 func (bifrost *Bifrost) getTracer() schemas.Tracer {
-	return bifrost.tracer.Load().(*tracerWrapper).tracer
+	if v := bifrost.tracer.Load(); v != nil {
+		if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+			return tw.tracer
+		}
+	}
+	return schemas.DefaultTracer()
 }

 func (bifrost *Bifrost) Shutdown() {
@@
-	// Stop the tracerWrapper to clean up background goroutines
-	if tracerWrapper := bifrost.tracer.Load().(*tracerWrapper); tracerWrapper != nil && tracerWrapper.tracer != nil {
-		tracerWrapper.tracer.Stop()
-	}
+	// Stop the tracerWrapper to clean up background goroutines
+	if v := bifrost.tracer.Load(); v != nil {
+		if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+			tw.tracer.Stop()
+		}
+	}

Also applies to: 95-107, 129-147, 253-265, 3893-3896

framework/tracing/helpers.go (1)

10-82: Helpers are clean; optionally guard against nil TraceStore

The context-based helpers are straightforward and defensive on missing trace/ID/span. The only additional hardening you might consider is an early if store == nil { return } in GetTrace, AddSpan, AddChildSpan, EndSpan, SetSpanAttribute, and AddSpanEvent to avoid panics if a nil store is ever passed in.

transports/bifrost-http/handlers/devpprof.go (1)

270-368: getTopAllocations provides synthetic data, not actual allocation hotspots.

The function attempts to provide allocation data but:

  1. runtime.Caller(i+2) captures the call stack of getTopAllocations itself, not actual allocation sites
  2. The "distributed estimate" calculation (HeapAlloc / (i+1)) produces arbitrary numbers
  3. The fallback to hardcoded runtime function names with unrelated memory stats doesn't reflect real allocation patterns

This is acceptable for a dev-only diagnostic tool, but the data should be clearly labeled as estimates in the API response or documentation to avoid misinterpretation.

Consider adding a comment in the PprofData struct or API documentation indicating that TopAllocations provides estimated/synthetic data rather than actual allocation profiling results. For accurate heap profiling, users should use go tool pprof directly.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 330f7f3 and 6fb3318.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (96)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • plugins/otel/ttlsyncmap.go
  • framework/configstore/tables/mcp.go
✅ Files skipped from review due to trivial changes (2)
  • framework/version
  • plugins/governance/version
🚧 Files skipped from review as they are similar to previous changes (35)
  • transports/bifrost-http/handlers/plugins.go
  • plugins/semanticcache/changelog.md
  • ui/components/ui/switch.tsx
  • examples/plugins/hello-world/go.mod
  • plugins/mocker/version
  • docs/plugins/getting-started.mdx
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • plugins/mocker/main.go
  • ui/components/ui/sheet.tsx
  • core/version
  • plugins/logging/utils.go
  • plugins/governance/go.mod
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/governance.go
  • plugins/semanticcache/main.go
  • core/schemas/context.go
  • .github/workflows/scripts/release-bifrost-http.sh
  • transports/bifrost-http/handlers/mcp.go
  • docs/plugins/migration-guide.mdx
  • transports/bifrost-http/handlers/websocket.go
  • plugins/logging/changelog.md
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/providers.go
  • plugins/maxim/main.go
  • transports/version
  • examples/plugins/hello-world/main.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/lib/config.go
  • plugins/semanticcache/version
  • framework/streaming/transcription.go
  • plugins/jsonparser/main.go
  • docs/docs.json
  • plugins/otel/version
  • framework/streaming/audio.go
  • plugins/logging/version
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • framework/streaming/responses.go
  • ui/lib/store/apis/index.ts
  • plugins/mocker/changelog.md
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/health.go
  • framework/streaming/types.go
  • plugins/maxim/version
  • plugins/jsonparser/changelog.md
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/integrations/router.go
  • framework/changelog.md
  • core/providers/utils/utils.go
  • plugins/otel/changelog.md
  • ui/lib/store/apis/devApi.ts
  • plugins/telemetry/changelog.md
  • docs/plugins/writing-plugin.mdx
  • plugins/governance/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • ui/components/devProfiler.tsx
  • transports/changelog.md
  • transports/bifrost-http/handlers/config.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/integrations.go
  • ui/components/sidebar.tsx
  • framework/tracing/helpers.go
  • plugins/governance/changelog.md
  • plugins/otel/main.go
  • framework/tracing/store.go
  • framework/plugins/dynamicplugin.go
  • core/schemas/tracer.go
  • transports/bifrost-http/handlers/inference.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/session.go
  • plugins/otel/converter.go
  • transports/bifrost-http/server/server.go
  • framework/streaming/accumulator.go
  • core/schemas/plugin.go
  • framework/tracing/propagation.go
  • transports/bifrost-http/lib/config_test.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • plugins/logging/main.go
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/maxim/changelog.md
  • framework/streaming/chat.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • ui/app/clientLayout.tsx
  • transports/bifrost-http/handlers/devpprof.go
  • core/changelog.md
  • plugins/jsonparser/version
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/health.go
  • framework/streaming/types.go
  • framework/plugins/dynamicplugin_test.go
  • transports/bifrost-http/integrations/router.go
  • core/providers/utils/utils.go
  • plugins/governance/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/integrations.go
  • framework/tracing/helpers.go
  • plugins/otel/main.go
  • framework/tracing/store.go
  • framework/plugins/dynamicplugin.go
  • core/schemas/tracer.go
  • transports/bifrost-http/handlers/inference.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/session.go
  • plugins/otel/converter.go
  • transports/bifrost-http/server/server.go
  • framework/streaming/accumulator.go
  • core/schemas/plugin.go
  • framework/tracing/propagation.go
  • transports/bifrost-http/lib/config_test.go
  • plugins/logging/main.go
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/mcpserver.go
  • framework/streaming/chat.go
  • framework/tracing/llmspan.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/handlers/devpprof.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/devpprof.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
🧬 Code graph analysis (31)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/health.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/components/devProfiler.tsx (3)
npx/bin.js (3)
  • k (178-178)
  • sizes (179-179)
  • data (172-172)
ui/lib/utils/port.ts (1)
  • isDevelopmentMode (110-112)
ui/components/ui/tooltip.tsx (1)
  • Tooltip (43-43)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
plugins/otel/main.go (2)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/trace.go (1)
  • Trace (10-19)
framework/tracing/store.go (3)
core/schemas/tracer.go (1)
  • Tracer (38-116)
core/schemas/bifrost.go (1)
  • BifrostResponse (335-355)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
core/schemas/tracer.go (3)
core/schemas/bifrost.go (4)
  • ModelProvider (33-33)
  • BifrostError (474-483)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/trace.go (2)
  • SpanKind (115-115)
  • SpanStatus (143-143)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
core/bifrost.go (6)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • DefaultTracer (181-183)
  • SpanHandle (11-11)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/bifrost.go (10)
  • ListModelsRequest (92-92)
  • BifrostRequest (173-193)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyStreamStartTime (145-145)
  • BifrostContextKeyTraceID (143-143)
  • BifrostResponse (335-355)
  • RequestType (89-89)
  • BifrostContextKeyPostHookSpanFinalizer (149-149)
  • BifrostError (474-483)
  • BifrostContextKeySpanID (144-144)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/context.go (1)
  • NewBifrostContextWithTimeout (70-73)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (15)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
transports/bifrost-http/lib/config_test.go (1)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (276-356)
ui/app/workspace/logs/views/logDetailsSheet.tsx (4)
ui/components/ui/sheet.tsx (1)
  • SheetContent (137-137)
ui/components/ui/alertDialog.tsx (9)
  • AlertDialog (83-83)
  • AlertDialogTrigger (93-93)
  • AlertDialogContent (86-86)
  • AlertDialogHeader (89-89)
  • AlertDialogTitle (92-92)
  • AlertDialogDescription (87-87)
  • AlertDialogFooter (88-88)
  • AlertDialogCancel (85-85)
  • AlertDialogAction (84-84)
ui/components/ui/dropdownMenu.tsx (5)
  • DropdownMenu (193-193)
  • DropdownMenuTrigger (207-207)
  • DropdownMenuContent (195-195)
  • DropdownMenuItem (197-197)
  • DropdownMenuSeparator (202-202)
ui/components/ui/button.tsx (1)
  • Button (70-70)
plugins/logging/main.go (3)
core/schemas/bifrost.go (3)
  • RequestType (89-89)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyTraceID (143-143)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (1)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
transports/bifrost-http/handlers/middlewares.go (6)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/chat.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
ui/app/clientLayout.tsx (1)
ui/components/devProfiler.tsx (1)
  • DevProfiler (55-422)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 3f130fd to e8ca91d Compare December 25, 2025 08:08
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 8

♻️ Duplicate comments (18)
framework/tracing/llmspan.go (1)

190-194: Namespace ExtraParams keys to avoid clobbering standard trace attributes

Across the various Populate*RequestAttributes helpers, ExtraParams is currently merged into attrs with user-supplied keys (e.g., attrs[k] = fmt.Sprintf("%v", v)). That means a caller can accidentally overwrite standard keys like gen_ai.request.model, gen_ai.usage.*, etc., by choosing the same key name in ExtraParams.

To keep the core attribute namespace stable while still exposing user data, consider:

  • Prefixing all extra keys (for example, attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)), and
  • Applying this consistently in all the ExtraParams loops (chat, text, embedding, responses, batch, and file operations).

Optionally, you can also guard against duplicate prefixed keys if you want to avoid overwriting on collision.

Also applies to: 298-301, 365-368, 575-577, 708-710, 739-741, 753-756, 768-771, 783-786, 958-961, 982-985, 997-1000, 1012-1015, 1027-1030

core/schemas/trace.go (1)

40-49: Guard Trace.Reset, Span.End, and Span.Reset with their mutexes to avoid data races

These methods mutate shared state (Trace fields and Span timing/status/collections) without acquiring the corresponding mutex, while other methods (AddSpan, SetAttribute, AddEvent) do take the lock. If traces/spans are used concurrently (which tracing callers commonly do), this will lead to data races when spans are ended or traces/spans are reset for pool reuse.

Consider wrapping each of these in the appropriate lock/unlock:

  • Trace.Reset: lock t.mu for the duration of the field/slice resets.
  • Span.End: lock s.mu while setting EndTime, Status, and StatusMsg.
  • Span.Reset: lock s.mu while clearing fields and truncating Events.

This keeps the concurrency model consistent with the rest of the API and avoids subtle pool-related races.

Proposed change
 func (t *Trace) Reset() {
-	t.TraceID = ""
-	t.ParentID = ""
-	t.RootSpan = nil
-	t.Spans = t.Spans[:0]
-	t.StartTime = time.Time{}
-	t.EndTime = time.Time{}
-	t.Attributes = nil
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.TraceID = ""
+	t.ParentID = ""
+	t.RootSpan = nil
+	t.Spans = t.Spans[:0]
+	t.StartTime = time.Time{}
+	t.EndTime = time.Time{}
+	t.Attributes = nil
 }

 // End marks the span as complete with the given status
 func (s *Span) End(status SpanStatus, statusMsg string) {
-	s.EndTime = time.Now()
-	s.Status = status
-	s.StatusMsg = statusMsg
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.EndTime = time.Now()
+	s.Status = status
+	s.StatusMsg = statusMsg
 }

 // Reset clears the span for reuse from pool
 func (s *Span) Reset() {
-	s.SpanID = ""
-	s.ParentID = ""
-	s.TraceID = ""
-	s.Name = ""
-	s.Kind = SpanKindUnspecified
-	s.StartTime = time.Time{}
-	s.EndTime = time.Time{}
-	s.Status = SpanStatusUnset
-	s.StatusMsg = ""
-	s.Attributes = nil
-	s.Events = s.Events[:0]
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.SpanID = ""
+	s.ParentID = ""
+	s.TraceID = ""
+	s.Name = ""
+	s.Kind = SpanKindUnspecified
+	s.StartTime = time.Time{}
+	s.EndTime = time.Time{}
+	s.Status = SpanStatusUnset
+	s.StatusMsg = ""
+	s.Attributes = nil
+	s.Events = s.Events[:0]
 }

Also applies to: 84-104

docs/plugins/migration-guide.mdx (1)

247-250: Verify v1.4.0 tag will exist at merge time.

The go get command references @v1.4.0, but this tag may not exist in the repository yet. Ensure the v1.4.0 tag is created before merging this PR, or update the documentation to use an existing version tag, a specific commit hash, or add a note indicating this is for an upcoming release.

Run this script to verify the tag status:

#!/bin/bash
# Check if v1.4.0 tag exists and show latest tags
git tag -l "v1.4.0"
echo "---"
echo "Latest tags:"
git tag -l --sort=-version:refname | head -10
transports/changelog.md (1)

16-16: Add language specifier to code fence.

The fenced code block is missing a language identifier, which prevents syntax highlighting and violates markdownlint rule MD040.

🔎 Proposed fix
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)
examples/plugins/hello-world/main.go (1)

19-27: HTTPTransportMiddleware sets fasthttp context value but PreHook/PostHook read from BifrostContext.

The middleware on line 23 sets ctx.SetUserValue(...) on fasthttp.RequestCtx, but PreHook (line 30) and PostHook (lines 39-40) attempt to read from *schemas.BifrostContext. These are separate context types, so the value will never propagate. This breaks the example's demonstration of data flow between hooks and middleware.

transports/bifrost-http/integrations/router.go (1)

514-514: Non-streaming handlers pass fasthttp.RequestCtx instead of bifrostCtx to Bifrost client.

All non-streaming client calls (lines 514, 534, 557, 580, 603, 626, 660) pass ctx (the *fasthttp.RequestCtx) instead of the converted *bifrostCtx. This breaks tracing, plugin context propagation, and may cause type errors if the client expects context.Context. The comment on line 503 even says "Use the cancellable context from ConvertToBifrostContext" but the code doesn't follow through.

Also applies to: 534-534, 557-557, 580-580, 603-603, 626-626, 660-660

framework/plugins/dynamicplugin.go (1)

140-147: Type mismatch: HTTPTransportMiddleware symbol is a factory function, not the middleware itself.

Line 145 attempts to cast the symbol directly to schemas.BifrostHTTPMiddleware, but the example plugin at examples/plugins/hello-world/main.go (lines 19-27) exports HTTPTransportMiddleware as a factory function:

func HTTPTransportMiddleware() schemas.BifrostHTTPMiddleware

The type assertion will fail at runtime. The symbol should be cast to func() schemas.BifrostHTTPMiddleware, then called to obtain the middleware.

core/providers/utils/utils.go (1)

1428-1428: Pass context by value, not by pointer.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. Contexts should always be passed by value. The context.Context type is designed to be passed by value and is safe for concurrent use.

plugins/logging/main.go (1)

225-233: Revisit PreHook’s tracer-based accumulator creation; likely dead with current tracer injection order.

In PreHook you gate CreateStreamAccumulator on:

if bifrost.IsStreamRequestType(req.RequestType) {
    if tracer, ok := ctx.Value(schemas.BifrostContextKeyTracer).(schemas.Tracer); ok && tracer != nil {
        if traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string); ok && traceID != "" {
            tracer.CreateStreamAccumulator(traceID, createdTimestamp)
        }
    }
}

But BifrostContextKeyTracer is injected by core inside executeRequestWithRetries, which runs on the worker side after plugin PreHooks have already executed. Unless another component sets BifrostContextKeyTracer earlier, this condition will almost never be true in the normal HTTP→Bifrost path, so the PreHook accumulator creation is effectively dead code.

Given that:

  • streaming aggregation now flows through tracer.ProcessStreamingChunk in PostHook, and
  • cleanup is explicitly delegated to the tracing middleware/tracer,

it would be cleaner and less confusing to either:

  • remove the PreHook CreateStreamAccumulator block and let the tracer lazily create accumulators inside ProcessStreamingChunk, or
  • move tracer injection earlier (before RunPreHooks) if you really want PreHook to own creation.

Right now the lifecycle is split in a way that’s hard to reason about and easy to drift.

Also applies to: 382-471, 615-625

plugins/governance/main.go (2)

233-261: Validate x-bf-vk with VirtualKeyPrefix like the other headers.

parseVirtualKey currently returns the x-bf-vk header verbatim without checking that it starts with VirtualKeyPrefix, while Authorization, x-api-key, and x-goog-api-key all enforce that prefix. This inconsistency allows malformed or non-virtual keys via x-bf-vk to bypass the basic format check.

Consider normalizing and validating x-bf-vk similarly before returning:

Suggested adjustment
 func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
 	var virtualKeyValue string
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
-	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
-	}
+	if raw := strings.TrimSpace(string(vkHeader)); raw != "" {
+		if strings.HasPrefix(strings.ToLower(raw), VirtualKeyPrefix) {
+			return bifrost.Ptr(raw)
+		}
+	}

This keeps all header paths aligned and avoids treating x-bf-vk as a privileged bypass.


291-307: Fix log message: this block is doing unmarshal, not marshal.

In the HTTP middleware body handling, the error log on sonic.Unmarshal still says "failed to marshal request body", which is misleading for debugging.

Suggested fix
-	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
-	if err != nil {
-		p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+	err = sonic.Unmarshal(ctx.Request.Body(), &payload)
+	if err != nil {
+		p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)
core/bifrost.go (1)

70-71: Harden tracer storage/access to avoid panics from nil or unexpected values.

tracer is stored in an atomic.Value as *tracerWrapper, but both getTracer() and Shutdown() unconditionally type‑assert:

return bifrost.tracer.Load().(*tracerWrapper).tracer
// ...
if tracerWrapper := bifrost.tracer.Load().(*tracerWrapper); tracerWrapper != nil && tracerWrapper.tracer != nil {
    tracerWrapper.tracer.Stop()
}

If Load() ever returns nil or a non‑*tracerWrapper (e.g., a misconfigured test or external code constructing Bifrost without calling Init properly), these assertions will panic. Earlier feedback on tracer storage raised a similar concern.

Consider adding defensive guards both when setting and when reading:

  • Ensure SetTracer always stores a non‑nil *tracerWrapper.
  • Make getTracer and Shutdown handle Load()==nil or wrong types by falling back to schemas.DefaultTracer() (for reads) and skipping Stop() (for shutdown), optionally with a debug/warn log.
Illustrative adjustment
func (bifrost *Bifrost) SetTracer(tracer schemas.Tracer) {
    if tracer == nil {
        tracer = schemas.DefaultTracer()
    }
-   bifrost.tracer.Store(&tracerWrapper{tracer: tracer})
+   bifrost.tracer.Store(&tracerWrapper{tracer: tracer})
}

func (bifrost *Bifrost) getTracer() schemas.Tracer {
-   return bifrost.tracer.Load().(*tracerWrapper).tracer
+   if v := bifrost.tracer.Load(); v != nil {
+       if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+           return tw.tracer
+       }
+   }
+   return schemas.DefaultTracer()
}

func (bifrost *Bifrost) Shutdown() {
    // ...
-   if tracerWrapper := bifrost.tracer.Load().(*tracerWrapper); tracerWrapper != nil && tracerWrapper.tracer != nil {
-       tracerWrapper.tracer.Stop()
-   }
+   if v := bifrost.tracer.Load(); v != nil {
+       if tw, ok := v.(*tracerWrapper); ok && tw != nil && tw.tracer != nil {
+           tw.tracer.Stop()
+       }
+   }
    // ...
}

This keeps the wrapper approach while making the public API resilient to accidental misuse and future refactors around tracer initialization.

Also applies to: 102-107, 118-147, 253-265, 3893-3896

transports/bifrost-http/handlers/devpprof.go (3)

142-150: Stop() can panic if called twice due to closing an already-closed channel.

The Stop() method closes c.stopCh without protection against double-close. If Stop() is called twice, it will panic. Additionally, Start() after Stop() will fail because stopCh remains closed.

🔎 Proposed fix using select to guard the close
 func (c *MetricsCollector) Stop() {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	if c.started {
+	if c.started && c.stopCh != nil {
+		select {
+		case <-c.stopCh:
+			// Already closed
+		default:
 		close(c.stopCh)
+		}
 		c.started = false
+		c.stopCh = nil
 	}
 }

176-190: getCPUSample uses Unix-only syscall.Getrusage and will not compile on Windows.

syscall.Getrusage is only available on Unix platforms. Since the project supports Windows (per platform-specific handling in server.go), this file needs a build constraint or platform-specific implementation.

Add a build constraint at the top of the file:

//go:build !windows
// +build !windows

Or create devpprof_unix.go and devpprof_windows.go with appropriate stubs.


370-376: devPprofHandler is created locally and never cleaned up, causing goroutine leaks.

In server.go (line 1022), devPprofHandler is instantiated as a local variable and RegisterRoutes starts the collector (line 373). Since the handler isn't stored on BifrostHTTPServer, its Cleanup() method is never called during shutdown, leaving the collector goroutine running indefinitely.

Store devPprofHandler as a field in BifrostHTTPServer and call Cleanup() in the shutdown sequence.

framework/tracing/store.go (1)

13-22: Remove unused Tracer and RequestID fields from DeferredSpanInfo.

The Tracer (line 17) and RequestID (line 18) fields are never assigned or used. StoreDeferredSpan() (lines 120-125) only initializes SpanID and StartTime. In contrast, FirstChunkTime and AccumulatedChunks are actively used in AppendStreamingChunk().

🔎 Proposed fix
 type DeferredSpanInfo struct {
 	SpanID             string
 	StartTime          time.Time
-	Tracer             schemas.Tracer // Reference to tracer for completing the span
-	RequestID          string         // Request ID for accumulator lookup
 	FirstChunkTime     time.Time      // Timestamp of first chunk (for TTFT calculation)
 	AccumulatedChunks  []*schemas.BifrostResponse // Accumulated streaming chunks
 	mu                 sync.Mutex     // Mutex for thread-safe chunk accumulation
 }
transports/bifrost-http/server/server.go (1)

959-959: Use s.GetGovernancePluginName() instead of hardcoded governance.PluginName.

Line 959 uses governance.PluginName directly, bypassing the enterprise plugin name override mechanism. Lines 510 and 657 correctly use s.GetGovernancePluginName() for the same operation.

🔎 Proposed fix
-	governancePlugin, _ := FindPluginByName[schemas.Plugin](s.Plugins, governance.PluginName)
+	governancePlugin, _ := FindPluginByName[schemas.Plugin](s.Plugins, s.GetGovernancePluginName())
framework/tracing/tracer.go (1)

393-398: Choice and tool-call indices are not sorted, causing nondeterministic output.

The comments say "Sort choice indices for deterministic output" (line 394) and "Sort tool calls by index" (line 433), but no actual sorting is performed. The iteration order over maps in Go is nondeterministic, so the output order will vary between runs.

🔎 Proposed fix - add sorting
+import "sort"

 // Build final choices from accumulated data
 // Sort choice indices for deterministic output
 choiceIndices := make([]int, 0, len(choiceMap))
 for idx := range choiceMap {
     choiceIndices = append(choiceIndices, idx)
 }
+sort.Ints(choiceIndices)

 // ... later ...

 // Sort tool calls by index
 tcIndices := make([]int, 0, len(accum.toolCalls))
 for tcIdx := range accum.toolCalls {
     tcIndices = append(tcIndices, tcIdx)
 }
+sort.Ints(tcIndices)

Also applies to: 433-437

🧹 Nitpick comments (5)
.github/workflows/scripts/release-bifrost-http.sh (1)

266-266: Consider sourcing the PostgreSQL password dynamically.

The password bifrost_password is hardcoded in the docker exec command. While this is acceptable for test environments, it creates a maintenance burden if the password in docker-compose.yml changes.

🔎 Suggested improvement

Extract the password from the docker-compose file or environment:

+# Extract PostgreSQL password from docker-compose or environment
+POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-bifrost_password}"
+
 docker exec -e PGPASSWORD=bifrost_password "$(docker compose -f "$CONFIGS_DIR/docker-compose.yml" ps -q postgres)" \

Or parse it from the compose file:

# Extract from docker-compose.yml
POSTGRES_PASSWORD=$(grep -A 5 'POSTGRES_PASSWORD' "$CONFIGS_DIR/docker-compose.yml" | grep -oP ':\s*\K\S+' | head -1)
POSTGRES_PASSWORD="${POSTGRES_PASSWORD:-bifrost_password}"
framework/streaming/accumulator.go (1)

104-127: Clarify StartTimestamp semantics and consider trimming redundant IsZero checks

createStreamAccumulator now always initializes StartTimestamp to time.Now(), while the add*StreamChunk methods still conditionally set StartTimestamp when it’s zero and separately track FirstChunkTimestamp for TTFT. In normal flows where accumulators are always created via createStreamAccumulator/CreateStreamAccumulator, those StartTimestamp.IsZero() branches will rarely, if ever, fire.

If accumulators aren’t constructed elsewhere with a zero StartTimestamp, consider either:

  • Removing the StartTimestamp.IsZero() blocks in the add*StreamChunk methods, or
  • Updating the comment above createStreamAccumulator to explicitly describe the “default to now, optionally overridden by CreateStreamAccumulator” behavior and that per-chunk code won’t back-fill StartTimestamp anymore.

This would make the TTFT/latency behavior easier to reason about and avoid dead code paths.

Also applies to: 129-223

framework/tracing/propagation.go (1)

72-84: Clarify ExtractParentID docstring to match what’s returned

The implementation intentionally returns the trace-id component (ctx.TraceID) from the incoming traceparent, not the parent/span ID. The current comment (“extracts the parent trace ID from W3C traceparent header”) can be read as span‑ID semantics; consider rephrasing to something like “extracts the trace ID from the parent request’s W3C traceparent header” to avoid confusion.

transports/bifrost-http/handlers/middlewares.go (1)

281-307: completeAndFlushTrace runs in a goroutine without error aggregation or timeout.

The trace completion and plugin injection runs asynchronously, which is good for not blocking the response. However:

  1. If plugin.Inject hangs, the goroutine will leak
  2. Errors are only logged, with no way to track injection failures across plugins

Consider adding a timeout context for plugin injection:

go func() {
    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    
    // ... existing cleanup code ...
    
    for _, plugin := range *m.obsPlugins.Load() {
        if plugin == nil {
            continue
        }
        if err := plugin.Inject(ctx, completedTrace); err != nil {
            logger.Warn("observability plugin %s failed to inject trace: %v", plugin.GetName(), err)
        }
    }
    // ...
}()
core/schemas/tracer.go (1)

9-11: SpanHandle as empty interface limits type safety.

Using interface{} for SpanHandle means any value can be passed, which could lead to runtime errors if incorrect types are used. This is intentional for flexibility across implementations but worth noting.

Consider documenting the expected handle structure or adding runtime type checks in implementations to fail fast on misuse. The current approach is acceptable given the interface-based design.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6fb3318 and 3f130fd.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (96)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • framework/configstore/tables/mcp.go
  • core/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (1)
  • plugins/telemetry/version
🚧 Files skipped from review as they are similar to previous changes (40)
  • framework/streaming/types.go
  • ui/lib/store/apis/index.ts
  • plugins/governance/version
  • transports/bifrost-http/handlers/ui.go
  • transports/version
  • plugins/otel/version
  • core/schemas/context.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/mcp.go
  • plugins/mocker/main.go
  • framework/version
  • plugins/logging/version
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/plugins.go
  • plugins/mocker/changelog.md
  • framework/plugins/dynamicplugin_test.go
  • .github/workflows/release-pipeline.yml
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/providers.go
  • plugins/maxim/changelog.md
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • plugins/mocker/version
  • ui/lib/store/apis/devApi.ts
  • ui/components/ui/switch.tsx
  • plugins/maxim/main.go
  • core/schemas/plugin.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • plugins/logging/utils.go
  • docs/plugins/writing-plugin.mdx
  • ui/components/ui/sheet.tsx
  • ui/app/clientLayout.tsx
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/logging.go
  • plugins/jsonparser/version
  • framework/streaming/chat.go
  • docs/docs.json
  • plugins/telemetry/main.go
  • plugins/semanticcache/version
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/session.go
  • plugins/semanticcache/changelog.md
  • plugins/jsonparser/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • plugins/governance/go.mod
  • transports/changelog.md
  • plugins/semanticcache/main.go
  • framework/tracing/propagation.go
  • transports/bifrost-http/lib/config_test.go
  • core/changelog.md
  • framework/plugins/dynamicplugin.go
  • plugins/logging/changelog.md
  • plugins/maxim/version
  • transports/bifrost-http/handlers/integrations.go
  • core/providers/utils/utils.go
  • docs/plugins/getting-started.mdx
  • transports/bifrost-http/lib/middleware.go
  • plugins/otel/changelog.md
  • framework/tracing/helpers.go
  • core/schemas/tracer.go
  • plugins/otel/main.go
  • transports/bifrost-http/integrations/router.go
  • plugins/telemetry/changelog.md
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/handlers/devpprof.go
  • framework/tracing/store.go
  • core/schemas/trace.go
  • ui/components/sidebar.tsx
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/governance/changelog.md
  • transports/bifrost-http/handlers/governance.go
  • plugins/otel/converter.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • examples/plugins/hello-world/main.go
  • framework/tracing/tracer.go
  • plugins/governance/main.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • core/version
  • framework/tracing/llmspan.go
  • transports/bifrost-http/server/server.go
  • core/bifrost.go
  • plugins/logging/main.go
  • ui/components/devProfiler.tsx
  • framework/streaming/transcription.go
  • core/bifrost_test.go
  • docs/plugins/migration-guide.mdx
  • framework/changelog.md
  • framework/streaming/accumulator.go
  • plugins/jsonparser/main.go
🧠 Learnings (6)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/semanticcache/main.go
  • framework/tracing/propagation.go
  • transports/bifrost-http/lib/config_test.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/integrations.go
  • core/providers/utils/utils.go
  • transports/bifrost-http/lib/middleware.go
  • framework/tracing/helpers.go
  • core/schemas/tracer.go
  • plugins/otel/main.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/devpprof.go
  • framework/tracing/store.go
  • core/schemas/trace.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/governance.go
  • plugins/otel/converter.go
  • core/schemas/bifrost.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • examples/plugins/hello-world/main.go
  • framework/tracing/tracer.go
  • plugins/governance/main.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/server/server.go
  • core/bifrost.go
  • plugins/logging/main.go
  • framework/streaming/transcription.go
  • core/bifrost_test.go
  • framework/streaming/accumulator.go
  • plugins/jsonparser/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-24T04:36:57.698Z
Learnt from: danpiths
Repo: maximhq/bifrost PR: 1169
File: transports/bifrost-http/handlers/governance.go:1708-1878
Timestamp: 2025-12-24T04:36:57.698Z
Learning: In governance update handlers (e.g., updateModelConfig, updateProviderGovernance), design updates to support clearing individual fields by sending null/empty values (e.g., {"rate_limit": {"token_max_limit": null}} clears token_max_limit). Follow this pattern for partial updates so users can remove specific governance settings without deleting the whole entity. Ensure budget updates follow the same approach using direct field assignment. Review input validation, JSON decoding (e.g., pointers vs values in Go structs), and API documentation to reflect nullable fields and expected behavior.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
🧬 Code graph analysis (26)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/lib/config_test.go (1)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (276-356)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/devpprof.go (4)
ui/lib/store/apis/devApi.ts (6)
  • MemoryStats (4-10)
  • CPUStats (13-17)
  • RuntimeStats (20-26)
  • AllocationInfo (29-35)
  • HistoryPoint (38-45)
  • PprofData (48-55)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/handlers/utils.go (1)
  • SendJSON (16-22)
framework/tracing/store.go (3)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (161-161)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/governance.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (15)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
core/schemas/bifrost.go (2)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/tracer.go (6)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • SpanHandle (11-11)
  • StreamAccumulatorResult (15-33)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (6)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
  • BifrostContextKeyRequestID (124-124)
  • BifrostContextKeyStreamEndIndicator (131-131)
core/schemas/context.go (2)
  • BifrostContext (32-42)
  • NewBifrostContext (47-65)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/llmspan.go (3)
core/schemas/bifrost.go (18)
  • BifrostRequest (173-193)
  • RequestType (89-89)
  • TextCompletionRequest (93-93)
  • EmbeddingRequest (99-99)
  • TranscriptionRequest (102-102)
  • SpeechRequest (100-100)
  • ResponsesRequest (97-97)
  • BatchCreateRequest (104-104)
  • BatchListRequest (105-105)
  • BatchRetrieveRequest (106-106)
  • BatchCancelRequest (107-107)
  • BatchResultsRequest (108-108)
  • FileUploadRequest (109-109)
  • FileListRequest (110-110)
  • FileRetrieveRequest (111-111)
  • FileDeleteRequest (112-112)
  • FileContentRequest (113-113)
  • BifrostResponse (335-355)
core/schemas/trace.go (19)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • AttrError (216-216)
  • AttrErrorType (217-217)
  • AttrErrorCode (218-218)
  • AttrInputMessages (222-222)
  • AttrResponseID (191-191)
  • AttrResponseModel (192-192)
  • AttrObject (197-197)
  • AttrSystemFprint (194-194)
  • AttrCreated (196-196)
  • AttrServiceTier (195-195)
  • AttrOutputMessages (225-225)
  • AttrFinishReason (193-193)
  • AttrPromptTokens (208-208)
  • AttrCompletionTokens (209-209)
  • AttrTotalTokens (210-210)
  • AttrPrompt (181-181)
  • AttrUsageCost (213-213)
core/schemas/chatcompletions.go (1)
  • BifrostChatRequest (12-19)
core/bifrost.go (6)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • DefaultTracer (181-183)
  • SpanHandle (11-11)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/bifrost.go (5)
  • BifrostRequest (173-193)
  • BifrostContextKeyTracer (146-146)
  • BifrostResponse (335-355)
  • RequestType (89-89)
  • BifrostError (474-483)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/context.go (1)
  • NewBifrostContextWithTimeout (70-73)
ui/components/devProfiler.tsx (1)
ui/lib/utils/port.ts (1)
  • isDevelopmentMode (110-112)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/bifrost_test.go (1)
core/schemas/tracer.go (1)
  • DefaultTracer (181-183)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
plugins/otel/main.go (1)

87-112: Fix OTEL resource attributes parsing: strings.SplitSeq + range usage won’t compile

Two problems here:

  1. strings.SplitSeq does not exist in the Go stdlib (and no alternative import is present), so this line won’t compile.
  2. for attribute := range ... iterates indexes, but strings.TrimSpace(attribute) expects a string; that’s a type mismatch.

This block should use strings.Split and capture the value, e.g.:

Proposed fix
-	if attributes, ok := os.LookupEnv(OTELResponseAttributesEnvKey); ok {
-		// We will split the attributes by , and then split each attribute by =
-		for attribute := range strings.SplitSeq(attributes, ",") {
-			attributeParts := strings.Split(strings.TrimSpace(attribute), "=")
+	if attributes, ok := os.LookupEnv(OTELResponseAttributesEnvKey); ok {
+		// We will split the attributes by , and then split each attribute by =
+		for _, attribute := range strings.Split(attributes, ",") {
+			attributeParts := strings.Split(strings.TrimSpace(attribute), "=")
 			if len(attributeParts) == 2 {
 				attributesFromEnvironment = append(
 					attributesFromEnvironment,
 					kvStr(strings.TrimSpace(attributeParts[0]), strings.TrimSpace(attributeParts[1])),
 				)
 			}
 		}
 	}
♻️ Duplicate comments (24)
examples/plugins/hello-world/go.mod (1)

10-22: Update transitive dependency versions to match the plugin stack.

The indirect dependencies still use outdated versions compared to the governance, logging, and telemetry plugins:

  • github.com/klauspost/compress should be v1.18.2 (line 16, currently v1.18.1)
  • golang.org/x/arch should be v0.23.0 (line 20, currently v0.22.0)
  • golang.org/x/sys should be v0.39.0 (line 21, currently v0.38.0)

Update these versions and run go mod tidy to ensure consistency across the plugin stack.

🔎 Proposed fix
 require (
 	github.com/andybalholm/brotli v1.2.0 // indirect
 	github.com/bytedance/gopkg v0.1.3 // indirect
 	github.com/bytedance/sonic v1.14.1 // indirect
 	github.com/bytedance/sonic/loader v0.3.0 // indirect
 	github.com/cloudwego/base64x v0.1.6 // indirect
-	github.com/klauspost/compress v1.18.1 // indirect
+	github.com/klauspost/compress v1.18.2 // indirect
 	github.com/klauspost/cpuid/v2 v2.3.0 // indirect
 	github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
 	github.com/valyala/bytebufferpool v1.0.0 // indirect
-	golang.org/x/arch v0.22.0 // indirect
-	golang.org/x/sys v0.38.0 // indirect
+	golang.org/x/arch v0.23.0 // indirect
+	golang.org/x/sys v0.39.0 // indirect
 )

Then run:

cd examples/plugins/hello-world && go mod tidy
transports/bifrost-http/handlers/devpprof.go (5)

1-15: Missing Unix-only build constraint for syscall.Getrusage usage.

The file imports syscall and uses syscall.Getrusage (line 178), which is only available on Unix platforms. Without build constraints, this code will fail to compile on Windows. As noted in previous reviews, the project supports Windows (evidenced by server.go).

This is a duplicate of the previous review comment on lines 176-190, but the issue has not been addressed.

Add build constraints at the top of the file:

+//go:build !windows
+// +build !windows
+
 package handlers

Or split into platform-specific files (devpprof_unix.go with the constraint and devpprof_windows.go with a Windows-compatible implementation).


142-150: Stop() doesn't recreate stopCh, breaking Start() after Stop().

This is a duplicate of the previous review comment. The issue persists: Stop() closes stopCh but never recreates it. If Start() is called after Stop(), the goroutine will exit immediately because stopCh is already closed.

Apply the previously suggested fix to make Stop() idempotent and allow Start() after Stop():

🔎 Proposed fix
 func (c *MetricsCollector) Stop() {
 	c.mu.Lock()
 	defer c.mu.Unlock()
-	if c.started {
+	if c.started && c.stopCh != nil {
+		select {
+		case <-c.stopCh:
+			// Already closed
+		default:
 		close(c.stopCh)
+		}
 		c.started = false
+		c.stopCh = nil
 	}
 }

And in Start(), recreate the channel:

 func (c *MetricsCollector) Start() {
 	c.mu.Lock()
 	if c.started {
 		c.mu.Unlock()
 		return
 	}
+	if c.stopCh == nil {
+		c.stopCh = make(chan struct{})
+	}
 	c.started = true
 	c.mu.Unlock()
 
 	go c.collectLoop()
 }

175-190: Unix-specific syscall usage breaks Windows compilation.

This is a duplicate of the previous review comment. The issue persists: syscall.Getrusage is only available on Unix platforms and will cause compilation failures on Windows.

This was already flagged in the review of lines 1-15. Apply the build constraint solution mentioned there.


270-368: getTopAllocations provides misleading placeholder data.

This is a duplicate of the previous review comment on lines 293-311. The issue persists: the function claims to return "top allocation sites" but uses runtime.Caller (which captures the current call stack, not allocation hotspots) and arbitrary divisions of heap statistics.

As previously suggested, either:

  1. Document clearly that this is placeholder/approximated data
  2. Use runtime/pprof to parse real heap profile data
  3. Remove top_allocations until accurate data can be provided

The current implementation may confuse users who expect real profiling data.


370-376: Collector started but never stopped, causing goroutine leak.

This is a duplicate of the previous review comment on lines 370-376. The issue persists: RegisterRoutes starts the collector goroutine, but since the handler is created as a local variable in server.go, its Cleanup() method is never called during server shutdown, leaking the background goroutine.

As previously suggested, store devPprofHandler as a field in BifrostHTTPServer and call its Cleanup() method in the shutdown sequence (around line 1330-1348 in server.go).

transports/changelog.md (1)

16-23: Add language specifier to fenced code block.

The code block is missing a language identifier, which prevents syntax highlighting and violates MD040. This was flagged in a previous review but remains unaddressed.

🔎 Add language specifier
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)

As per static analysis hints.

core/providers/utils/utils.go (1)

1424-1513: Refactor completeDeferredSpan to take context.Context by value instead of *context.Context

completeDeferredSpan only reads from the context (trace ID, tracer, post‑hook span finalizer) and does not need to mutate it. Passing *context.Context here is unconventional in Go and unnecessary, since the local ctx value in the callers is already being updated via the pointer passed to postHookRunner.

You can simplify and align with Go context best practices by changing the helper to take ctx context.Context and updating the call sites accordingly.

Proposed refactor
-// completeDeferredSpan completes the deferred LLM span for streaming requests.
-// This is called when the final chunk is processed (when StreamEndIndicator is true).
-// It retrieves the deferred span handle from TraceStore using the trace ID from context,
-// populates response attributes from accumulated chunks, and ends the span.
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
-		return
-	}
+// completeDeferredSpan completes the deferred LLM span for streaming requests.
+// This is called when the final chunk is processed (when StreamEndIndicator is true).
+// It retrieves the deferred span handle from TraceStore using the trace ID from context,
+// populates response attributes from accumulated chunks, and ends the span.
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == nil {
+		return
+	}
 
 	// Get the trace ID from context (this IS available in the provider's goroutine)
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
@@
-	tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+	tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
@@
-	if finalizer, ok := (*ctx).Value(schemas.BifrostContextKeyPostHookSpanFinalizer).(func(context.Context)); ok && finalizer != nil {
+	if finalizer, ok := ctx.Value(schemas.BifrostContextKeyPostHookSpanFinalizer).(func(context.Context)); ok && finalizer != nil {
@@
-			finalizerCtx := context.WithValue(*ctx, schemas.BifrostContextKeySpanID, spanID)
+			finalizerCtx := context.WithValue(ctx, schemas.BifrostContextKeySpanID, spanID)
 			finalizer(finalizerCtx)
 		} else {
-			finalizer(*ctx)
+			finalizer(ctx)
 		}
 	}
@@
-	tracer.ClearDeferredSpan(traceID)
+	tracer.ClearDeferredSpan(traceID)
 }

And adjust the call sites:

-				completeDeferredSpan(&ctx, processedResponse, processedError)
+				completeDeferredSpan(ctx, processedResponse, processedError)

(similarly for all other completeDeferredSpan(&ctx, ...) calls).

In Go, what are the recommended conventions for passing `context.Context`—by value or by pointer—and are there any official guidelines discouraging `*context.Context` parameters?
plugins/jsonparser/version (1)

1-1: Re‑check version bump level vs documented breaking changes

The version is bumped to 1.3.53 even though the plugin API changed from TransportInterceptor to HTTPTransportMiddleware, which is called out as breaking in docs. If you intend to follow semver for this plugin, consider promoting this to at least a minor bump (e.g., 1.4.0) for clarity, and keep it consistent with other plugins in this stack.

#!/bin/bash
# Compare version files across plugins to ensure consistent bump strategy for the middleware v2 change.
fd --type f 'version$' plugins | xargs -I{} sh -c 'echo "{}: $(cat "{}")'
docs/plugins/migration-guide.mdx (1)

247-250: Version reference issue (duplicate concern).

Line 249 references @v1.4.0, but as noted in previous review comments, this tag does not exist in the repository yet. Before merging, either:

  1. Create the v1.4.0 release tag, or
  2. Update the documentation to reference an existing version tag, or
  3. Use a commit hash instead

This ensures the go get command works when users follow the guide.

framework/changelog.md (1)

1-1: Fix grammar error in the tracing feature entry.

The phrase "to enabling e2e tracing" should be "to enable e2e tracing".

🔎 Proposed fix
-- feat: adds new tracing framework for allowing plugins to enabling e2e tracing
+- feat: adds new tracing framework for allowing plugins to enable e2e tracing
examples/plugins/hello-world/main.go (1)

19-27: Context value propagation is broken between middleware and hooks.

The middleware sets a value on fasthttp.RequestCtx via SetUserValue, but PreHook (line 30) and PostHook (line 39) read from schemas.BifrostContext using ctx.Value(). These are different context types, so the value will always be nil in the hooks.

Either propagate the value through the Bifrost context (if the framework supports extracting it from RequestCtx user values), or remove the context reads from the hooks if the data flow demonstration is no longer needed.

transports/bifrost-http/integrations/router.go (1)

508-508: Non-streaming path uses raw ctx instead of enriched bifrostCtx.

The non-streaming client calls pass ctx (*fasthttp.RequestCtx) while the comment at line 503 says "Use the cancellable context from ConvertToBifrostContext". This inconsistency was flagged in a previous review. The streaming and batch/file handlers correctly use the Bifrost context, but non-streaming inference calls do not.

Also applies to: 514-514, 534-534, 557-557, 580-580, 603-603, 626-626, 660-660

plugins/governance/main.go (1)

292-297: Fix error message: "marshal" should be "unmarshal".

Line 294 logs "failed to marshal" but the operation on line 292 is sonic.Unmarshal. The error message should reflect the actual operation.

🔎 Proposed fix
 			err = sonic.Unmarshal(ctx.Request.Body(), &payload)
 			if err != nil {
-				p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+				p.logger.Error("failed to unmarshal request body: %v", err)
 				next(ctx)
 				return
 			}
framework/tracing/store.go (1)

13-177: TraceStore implementation and streaming accumulation look correct; consider trimming unused fields

The TraceStore’s responsibilities — pooled trace/span allocation, TTL-based cleanup via ticker + stopOnce, deferred span storage, and TTFT calculation from StartTime/FirstChunkTime — are implemented in a thread-safe and straightforward way. The Stop() fix with sync.Once also avoids double-close panics.

One minor nit: in DeferredSpanInfo, the Tracer and RequestID fields are still never written or read anywhere in this file. Unless other packages rely on them (none were found in prior analysis), you can safely remove them to reduce confusion and per-entry footprint.

Also applies to: 244-371

plugins/otel/converter.go (1)

148-171: Guard uint64int64 cast in anyToKeyValue to avoid overflow

The case uint64 branch does a direct int64(v) cast, which will overflow for values larger than math.MaxInt64. Even if unlikely, this can silently corrupt attributes for large counters or IDs.

Consider clamping or logging when overflow would occur, e.g.:

Proposed fix
+import "math"
@@ func anyToKeyValue(key string, value any) *KeyValue {
-	case uint64:
-		return kvInt(key, int64(v))
+	case uint64:
+		if v > math.MaxInt64 {
+			// Optionally: log here via plugin logger
+			return kvInt(key, math.MaxInt64)
+		}
+		return kvInt(key, int64(v))
transports/bifrost-http/handlers/middlewares.go (1)

191-279: Fix TracingMiddleware to pass a context.Context into StartSpan (currently uses *fasthttp.RequestCtx)

TracingMiddleware.Middleware currently does:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

Here ctx is *fasthttp.RequestCtx, but StartSpan is defined on the tracer as:

StartSpan(ctx context.Context, name string, kind SpanKind) (context.Context, SpanHandle)

*fasthttp.RequestCtx does not implement context.Context, so this will not compile and also breaks the GetTraceID-based lookup that expects a real context.Context.

A safe pattern is to build a proper context.Context seeded with the new trace ID and use that for the span, while still storing the trace ID on the fasthttp ctx for downstream handlers:

Proposed fix
@@ func (m *TracingMiddleware) Middleware() schemas.BifrostHTTPMiddleware {
-			// Extract parent trace ID from W3C headers (if present)
-			parentID := tracing.ExtractParentID(&ctx.Request.Header)
-			// Create trace in store - only ID returned (trace data stays in store)
-			traceID := m.tracer.Load().CreateTrace(parentID)
-			// Only trace ID goes into context (lightweight, no bloat)
-			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
+			// Extract parent trace ID from W3C headers (if present)
+			parentID := tracing.ExtractParentID(&ctx.Request.Header)
+			// Create trace in store - only ID returned (trace data stays in store)
+			traceID := m.tracer.Load().CreateTrace(parentID)
+			// Store trace ID on fasthttp context for downstream handlers
+			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
@@
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Create root span for the HTTP request using a proper context.Context
+			spanBaseCtx := context.WithValue(context.Background(), schemas.BifrostContextKeyTraceID, traceID)
+			tr := m.tracer.Load()
+			spanCtx, rootSpan := tr.StartSpan(spanBaseCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
@@
-				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
+				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
 					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
 				}
 			}

Also consider caching tr := m.tracer.Load() once at the top of the handler and reusing it to avoid repeated atomic loads and to simplify nil checks.

#!/bin/bash
# Verify Tracer.StartSpan signature and ensure it expects context.Context
rg -n "type Tracer interface" core/schemas/tracer.go -n -A10
rg -n "func (t \*Tracer) StartSpan" framework/tracing/tracer.go -n -A5

Also applies to: 281-307

framework/tracing/llmspan.go (1)

190-193: ExtraParams keys may collide with standard attribute names.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing. If a user sets a key like "gen_ai.request.model" in ExtraParams, it would overwrite the standard attribute set earlier in the function.

This pattern repeats throughout the file at lines 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, and 1027-1029.

🔎 Suggested approach

Consider namespacing ExtraParams keys to prevent collisions:

 	// ExtraParams
 	for k, v := range req.Params.ExtraParams {
-		attrs[k] = fmt.Sprintf("%v", v)
+		attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 	}

Apply this pattern consistently across all ExtraParams handling locations.

core/schemas/trace.go (3)

40-49: Trace.Reset() lacks mutex protection.

This method modifies all fields including Spans without holding the mutex, creating a potential data race if Reset is called while another goroutine is accessing the trace (e.g., via AddSpan or GetSpan).


84-89: Span.End() method lacks mutex protection.

Unlike SetAttribute and AddEvent, the End method modifies EndTime, Status, and StatusMsg without holding the mutex. If End is called concurrently with reads or writes to these fields, a data race may occur.


91-104: Span.Reset() lacks mutex protection.

Similar to Trace.Reset(), this method modifies fields without holding the mutex. If reused from a pool while another goroutine still holds a reference, a data race could occur.

transports/bifrost-http/server/server.go (2)

1021-1026: Dev pprof handler created locally and never cleaned up.

When IsDevMode() is true, devPprofHandler is created locally and its RegisterRoutes starts the collector goroutine. Since the handler isn't stored on the server, Cleanup() is never called during shutdown, causing a resource leak.


1243-1252: Add TraceStore cleanup during server shutdown.

The TraceStore is initialized with a cleanup goroutine (line 1245) but is never cleaned up during shutdown. The traceStore variable is local-scoped and the underlying TraceStore.Stop() method is not called. This cleanup goroutine and its associated ticker will continue running indefinitely, causing a resource leak.

Store traceStore in the server struct and call Stop() in the shutdown sequence (lines 1299-1334), or add a public cleanup method to Tracer that calls store.Stop() and invoke it from Bifrost.Shutdown().

framework/tracing/tracer.go (1)

394-442: Choice and tool-call indices are not sorted, causing non-deterministic output.

Lines 394-398 build choiceIndices from map keys with a comment "Sort choice indices for deterministic output" but no sorting is performed. Similarly, lines 434-437 build tcIndices with a comment "Sort tool calls by index" but again no sorting occurs.

Map iteration order in Go is random, so the output order of choices and tool calls will vary across runs, making traces inconsistent.

🔎 Proposed fix
+	sort.Ints(choiceIndices)
+
 	for _, idx := range choiceIndices {
 		accum := choiceMap[idx]
 
 		// Build message
 		msg := &schemas.ChatMessage{
 			Role: accum.role,
 		}
 
 		// Set content
 		if accum.content != "" {
 			msg.Content = &schemas.ChatMessageContent{
 				ContentStr: &accum.content,
 			}
 		}
 
 		// Build assistant message fields
 		if accum.refusal != "" || accum.reasoning != "" || len(accum.reasoningDetails) > 0 ||
 			accum.audio != nil || len(accum.toolCalls) > 0 {
 			msg.ChatAssistantMessage = &schemas.ChatAssistantMessage{}
 
 			if accum.refusal != "" {
 				msg.ChatAssistantMessage.Refusal = &accum.refusal
 			}
 			if accum.reasoning != "" {
 				msg.ChatAssistantMessage.Reasoning = &accum.reasoning
 			}
 			if len(accum.reasoningDetails) > 0 {
 				msg.ChatAssistantMessage.ReasoningDetails = accum.reasoningDetails
 			}
 			if accum.audio != nil {
 				msg.ChatAssistantMessage.Audio = accum.audio
 			}
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
 					toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
 				}
🧹 Nitpick comments (4)
ui/lib/store/apis/devApi.ts (1)

59-59: Clarify polling configuration in comment.

The comment "polls every 10 seconds" is misleading—the endpoint itself doesn't configure polling. The polling interval is set by the consumer when calling the hook (e.g., useGetDevPprofQuery({}, { pollingInterval: 10000 })).

🔎 Suggested comment clarification
-    // Get dev pprof data - polls every 10 seconds
+    // Get dev pprof data - consumers can configure polling via pollingInterval option
     getDevPprof: builder.query<PprofData, void>({
plugins/logging/changelog.md (1)

1-19: Changelog clearly documents the HTTPTransportMiddleware breaking change

The description of the old vs new signatures and the required core/framework versions is accurate and aligned with the updated Plugin interface. If you expect some consumers to have called TransportInterceptor directly, you might soften “no code changes are required” to clarify it applies to users configuring the plugin via Bifrost rather than invoking its methods directly.

framework/tracing/propagation.go (1)

150-162: Add validation for traceFlags parameter.

FormatTraceparent defaults empty traceFlags to "00" but doesn't validate non-empty inputs. Per W3C Trace Context spec, trace flags must be exactly 2 hex characters. Invalid traceFlags values will produce non-compliant headers.

🔎 Proposed fix
 func FormatTraceparent(traceID, spanID, traceFlags string) string {
 	normalizedTraceID := normalizeTraceID(traceID)
 	normalizedSpanID := normalizeSpanID(spanID)

 	if normalizedTraceID == "" || normalizedSpanID == "" {
 		return ""
 	}

-	if traceFlags == "" {
-		traceFlags = "00" // Default: not sampled
+	// Validate and normalize traceFlags
+	if traceFlags == "" || len(traceFlags) != 2 || !isHex(traceFlags) {
+		traceFlags = "00" // Default: not sampled (or invalid input)
 	}
+	traceFlags = strings.ToLower(traceFlags)
 	return "00-" + normalizedTraceID + "-" + normalizedSpanID + "-" + traceFlags
 }
core/bifrost.go (1)

70-82: Tracer storage via atomic.Value + tracerWrapper is consistent; consider minor defensive checks

Wiring the tracer as:

  • tracer field using atomic.Value,
  • storing a single concrete type *tracerWrapper,
  • defaulting to schemas.DefaultTracer() when unset,

is a clean way to avoid atomic.Value’s concrete-type panics and ensure a non-nil tracer implementation is always available. SetTracer and getTracer() are consistent with that pattern, and Shutdown now correctly calls Stop() on the underlying tracer.

If you expect Bifrost instances to ever be constructed outside Init, you might defensively:

  • Guard bifrost.tracer.Load() against nil in getTracer()/Shutdown, and
  • Log when falling back to DefaultTracer().

As written, it’s fine for the current Init-only construction flow.

Also applies to: 102-147, 253-265, 3893-3896

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3f130fd and e8ca91d.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
📒 Files selected for processing (96)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (2)
  • plugins/otel/version
  • framework/version
🚧 Files skipped from review as they are similar to previous changes (35)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/streaming/types.go
  • transports/bifrost-http/handlers/health.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • transports/bifrost-http/handlers/websocket.go
  • plugins/governance/version
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/plugins.go
  • plugins/semanticcache/version
  • docs/docs.json
  • framework/tracing/helpers.go
  • framework/plugins/dynamicplugin_test.go
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • core/schemas/bifrost.go
  • core/version
  • ui/components/ui/sheet.tsx
  • .github/workflows/scripts/release-bifrost-http.sh
  • plugins/mocker/version
  • transports/bifrost-http/lib/config_test.go
  • ui/components/devProfiler.tsx
  • plugins/governance/go.mod
  • ui/components/sidebar.tsx
  • plugins/maxim/version
  • plugins/jsonparser/main.go
  • framework/streaming/chat.go
  • ui/components/ui/switch.tsx
  • transports/bifrost-http/handlers/ui.go
  • plugins/telemetry/changelog.md
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • ui/lib/store/apis/index.ts
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/logging/version
  • transports/changelog.md
  • transports/bifrost-http/integrations/router.go
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • transports/bifrost-http/handlers/devpprof.go
  • plugins/maxim/changelog.md
  • plugins/jsonparser/changelog.md
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • framework/tracing/store.go
  • core/providers/utils/utils.go
  • core/schemas/tracer.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • ui/app/clientLayout.tsx
  • plugins/semanticcache/changelog.md
  • framework/plugins/dynamicplugin.go
  • plugins/jsonparser/version
  • plugins/logging/main.go
  • plugins/governance/main.go
  • framework/streaming/transcription.go
  • core/changelog.md
  • transports/bifrost-http/handlers/inference.go
  • plugins/logging/utils.go
  • framework/tracing/propagation.go
  • ui/lib/store/apis/devApi.ts
  • docs/plugins/writing-plugin.mdx
  • plugins/governance/changelog.md
  • framework/streaming/accumulator.go
  • plugins/mocker/changelog.md
  • plugins/otel/changelog.md
  • transports/bifrost-http/handlers/integrations.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/logging.go
  • examples/plugins/hello-world/main.go
  • core/schemas/trace.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • framework/tracing/tracer.go
  • examples/plugins/hello-world/go.mod
  • transports/bifrost-http/lib/config.go
  • plugins/otel/main.go
  • plugins/logging/changelog.md
  • transports/version
  • core/schemas/plugin.go
  • core/schemas/context.go
  • plugins/maxim/main.go
  • framework/tracing/llmspan.go
  • framework/streaming/responses.go
  • framework/changelog.md
  • transports/bifrost-http/server/server.go
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/integrations/router.go
  • plugins/semanticcache/main.go
  • plugins/otel/converter.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • framework/tracing/store.go
  • core/providers/utils/utils.go
  • core/schemas/tracer.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • framework/plugins/dynamicplugin.go
  • plugins/logging/main.go
  • plugins/governance/main.go
  • framework/streaming/transcription.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/logging/utils.go
  • framework/tracing/propagation.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/integrations.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/logging.go
  • examples/plugins/hello-world/main.go
  • core/schemas/trace.go
  • core/bifrost_test.go
  • transports/bifrost-http/handlers/mcpserver.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/lib/config.go
  • plugins/otel/main.go
  • core/schemas/plugin.go
  • core/schemas/context.go
  • plugins/maxim/main.go
  • framework/tracing/llmspan.go
  • framework/streaming/responses.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/server/server.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
🧬 Code graph analysis (28)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
plugins/otel/converter.go (1)
core/schemas/trace.go (16)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/devpprof.go (4)
ui/lib/store/apis/devApi.ts (6)
  • MemoryStats (4-10)
  • CPUStats (13-17)
  • RuntimeStats (20-26)
  • AllocationInfo (29-35)
  • HistoryPoint (38-45)
  • PprofData (48-55)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/handlers/utils.go (1)
  • SendJSON (16-22)
transports/bifrost-http/handlers/middlewares.go (7)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/store.go (5)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
core/schemas/bifrost.go (1)
  • BifrostResponse (335-355)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
core/schemas/tracer.go (6)
core/schemas/bifrost.go (4)
  • ModelProvider (33-33)
  • BifrostError (474-483)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
core/schemas/context.go (1)
  • BifrostContext (32-42)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/app/clientLayout.tsx (1)
ui/components/devProfiler.tsx (1)
  • DevProfiler (55-422)
plugins/logging/main.go (5)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/bifrost.go (3)
  • RequestType (89-89)
  • BifrostContextKeyTracer (146-146)
  • BifrostContextKeyTraceID (143-143)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
framework/streaming/types.go (1)
  • ProcessedStreamResponse (118-126)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
ui/lib/store/apis/devApi.ts (1)
transports/bifrost-http/handlers/devpprof.go (6)
  • MemoryStats (27-33)
  • CPUStats (36-40)
  • RuntimeStats (43-49)
  • AllocationInfo (52-58)
  • HistoryPoint (61-68)
  • PprofData (71-78)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/bifrost_test.go (1)
core/schemas/tracer.go (1)
  • DefaultTracer (181-183)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (2)
core/schemas/plugin.go (1)
  • ObservabilityPlugin (123-137)
core/schemas/trace.go (1)
  • Trace (10-19)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/trace.go (1)
  • Trace (10-19)
core/schemas/context.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (147-147)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/server/server.go (8)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • ObservabilityPlugin (123-137)
plugins/governance/main.go (1)
  • PluginName (23-23)
plugins/logging/main.go (1)
  • PluginName (22-22)
plugins/telemetry/main.go (1)
  • PluginName (21-21)
transports/bifrost-http/handlers/devpprof.go (2)
  • IsDevMode (107-109)
  • NewDevPprofHandler (123-127)
transports/bifrost-http/handlers/middlewares.go (1)
  • NewTracingMiddleware (207-215)
framework/tracing/store.go (1)
  • NewTraceStore (40-67)
framework/tracing/tracer.go (1)
  • NewTracer (24-29)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from e8ca91d to 5186ad3 Compare December 25, 2025 13:55
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

♻️ Duplicate comments (18)
plugins/jsonparser/version (1)

1-1: Semver violation remains unresolved; stack targets v1.4.0.

The version 1.3.53 still represents a patch increment despite the breaking change (TransportInterceptor → HTTPTransportMiddleware) documented in the changelog. This was previously flagged and remains unaddressed.

Given that this PR stack targets the v1.4.0 branch and introduces framework-wide breaking changes across multiple plugins, consider whether:

  • The jsonparser plugin version should align with the Bifrost v1.4.0 release version, or
  • Individual plugin versions should independently reflect their breaking changes (e.g., bump to 2.0.0 or align with 1.4.0)

Verify the project's plugin versioning strategy across the entire PR stack (#1170, #1169, #1121, #1120, #1119, #888) to ensure consistency.

ui/components/ui/sheet.tsx (2)

103-103: Past review suggestion remains unaddressed.

The past review suggested moving mb-6 to the false branch of the ternary to avoid applying it when expandable is true (where p-0 could conflict). The current implementation still applies mb-6 unconditionally. If this deviation from the suggested fix is intentional, consider adding a brief comment explaining the decision.

🔎 Original suggested fix for reference
-		<div data-slot="sheet-header" className={cn("mb-6 flex items-center", sheetContext?.expandable ? "p-0" : "")} {...props}>
+		<div data-slot="sheet-header" className={cn("flex items-center", sheetContext?.expandable ? "p-0" : "mb-6")} {...props}>

114-114: Consider making left margin conditional on button presence.

The unconditional ml-3 creates left spacing even when the expand/collapse button isn't rendered (i.e., when expandable is false or side is not "right"). If consistent spacing is intentional regardless of button presence, the current implementation is fine. Otherwise, consider the conditional approach from the past review.

🔎 Original suggested fix for reference
-			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center ml-3", className)}>{children}</div>
+			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center", sheetContext?.expandable && sheetContext?.side === "right" ? "ml-3" : "", className)}>{children}</div>
plugins/otel/changelog.md (1)

19-19: Verify the migration guide file exists in this PR.

The changelog references /docs/plugins/migration-guide. Based on the AI summary, docs/plugins/migration-guide.mdx appears to be added in this PR. Ensure this file is included and the link resolves correctly after merge.

#!/bin/bash
# Verify migration guide file exists
fd "migration-guide" docs/
transports/changelog.md (2)

16-23: Version reference inconsistency with plugin changelogs.

The migration summary states v1.3.x (removed) and v1.4.x+ (new), but the plugin changelogs (maxim, jsonparser, semanticcache) consistently reference core v1.3.0+ for the new API. This inconsistency could confuse developers about which version to target for migration.

Consider aligning with the plugin changelogs: change v1.4.x+ (new) to v1.3.0+ (new) and adjust v1.3.x (removed) to the prior version series (e.g., v1.2.x (removed)).


16-16: Add language specifier to fenced code block.

The fenced code block lacks a language identifier, preventing proper syntax highlighting and violating MD040. Add text or go after the opening backticks:

🔎 Suggested fix
   **Migration summary:**
-  ```
+  ```text
   // v1.3.x (removed)
framework/changelog.md (1)

1-1: Fix grammar and expand tracing framework description.

Line 1 contains a grammar error: "to enabling e2e tracing" should be "to enable e2e tracing". Additionally, consider expanding this entry to briefly explain what the tracing framework provides to plugins (e.g., "enables plugins to report spans and traces for distributed tracing integration").

core/providers/utils/utils.go (1)

876-931: Streaming span completion logic looks correct; consider future cleanup of pointer‑context pattern.

The updated ProcessAndSendResponse / ProcessAndSendBifrostError now:

  • Record chunks into the tracer before post‑hooks.
  • Run postHookRunner first so completeDeferredSpan sees post‑processed data.
  • Complete the deferred span both on normal final chunks and on skip‑stream control, using the processed response/error.
  • Use tracer.GetAccumulatedChunks to populate response attributes, TTFT, and total chunks, then finalize plugin post‑hook spans before ending the llm.call span.

ProviderSendsDoneMarker now treating HuggingFace as non‑[DONE] is consistent with prior guidance for providers that terminate without the sentinel. Based on learnings, this is the desired behavior.

The remaining use of *context.Context in completeDeferredSpan follows the existing PostHookRunner pattern; if you revisit that API later, passing context.Context by value would better align with Go conventions, but it’s not blocking for this change.

Also applies to: 938-981, 1424-1513

transports/bifrost-http/integrations/router.go (1)

502-685: Use the converted Bifrost context in non‑streaming client calls, not the HTTP request context.

handleNonStreamingRequest still passes the raw *fasthttp.RequestCtx (ctx) into Bifrost client methods:

listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, ...)
textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, ...)
chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, ...)
responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, ...)
embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, ...)
speechResponse, bifrostErr := g.client.SpeechRequest(ctx, ...)
transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, ...)

Yet the comment above and the rest of this file show the intent is to use the cancellable context from ConvertToBifrostContext:

  • Streaming path correctly uses streamCtx := *bifrostCtx for all *StreamRequest calls.
  • Batch and file handlers use requestCtx := *bifrostCtx for their Bifrost calls.

Passing ctx here bypasses the enriched Bifrost context (trace IDs, plugin values, timeouts), so non‑streaming requests won’t participate fully in tracing/governance/plugin flows and may not respect cancellation semantics.

Consider updating these calls to use *bifrostCtx instead:

- listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+ listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)

- textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+ textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(*bifrostCtx, bifrostReq.TextCompletionRequest)

- chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+ chatResponse, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq.ChatRequest)

- responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+ responsesResponse, bifrostErr := g.client.ResponsesRequest(*bifrostCtx, bifrostReq.ResponsesRequest)

- embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+ embeddingResponse, bifrostErr := g.client.EmbeddingRequest(*bifrostCtx, bifrostReq.EmbeddingRequest)

- speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+ speechResponse, bifrostErr := g.client.SpeechRequest(*bifrostCtx, bifrostReq.SpeechRequest)

- transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+ transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(*bifrostCtx, bifrostReq.TranscriptionRequest)

This keeps non‑streaming behavior consistent with streaming, batch, and file flows, and ensures all observability/governance context travels through Bifrost.

framework/tracing/propagation.go (1)

10-29: W3C trace context helpers are well‑structured and align with the intended propagation semantics.

  • normalizeTraceID / normalizeSpanID enforce hex formatting and lengths, handling UUID inputs cleanly.
  • ParseTraceparent and FormatTraceparent correctly validate IDs and now normalize traceFlags, defaulting to "00" when invalid, which avoids emitting malformed headers.
  • ExtractParentID intentionally returns the parsed trace ID from the incoming traceparent for trace continuation, which matches the stored guidance for this file.

Overall this module provides a solid basis for W3C trace context handling in the tracing stack.

Also applies to: 58-84, 104-143, 145-182

plugins/governance/main.go (1)

233-261: Align virtual‑key validation across headers and fix misleading error logs in HTTPTransportMiddleware.

Two issues here:

  1. x-bf-vk is not validated against VirtualKeyPrefix.

    • parseVirtualKey returns x-bf-vk directly if non‑empty, without checking that it starts with VirtualKeyPrefix (unlike the Authorization / x-api-key / x-goog-api-key branches).
    • This makes VK validation inconsistent and can let malformed values bypass the usual prefix check, affecting governance enforcement and store lookups.

    Consider tightening this branch to mirror the other header sources:

    func parseVirtualKey(ctx *fasthttp.RequestCtx) *string {
        var virtualKeyValue string
  •  vkHeader := ctx.Request.Header.Peek("x-bf-vk")
    
  •  if string(vkHeader) != "" {
    
  •      return bifrost.Ptr(string(vkHeader))
    
  •  }
    
  •  vkHeader := string(ctx.Request.Header.Peek("x-bf-vk"))
    
  •  if vkHeader != "" {
    
  •      v := strings.TrimSpace(vkHeader)
    
  •      if strings.HasPrefix(strings.ToLower(v), VirtualKeyPrefix) {
    
  •          return bifrost.Ptr(v)
    
  •      }
    
  •  }
     // ... existing Authorization / x-api-key / x-goog-api-key logic ...
    
    }
    
    
  1. Unmarshal log message says "marshal".

    • In HTTPTransportMiddleware, the first error log in this block:

      err = sonic.Unmarshal(ctx.Request.Body(), &payload)
      if err != nil {
          p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
      }

      is performing an unmarshal, not a marshal. This was already raised earlier; the wording is still misleading.

    A minimal fix:

    - p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
    + p.logger.Error("failed to unmarshal request body to check for virtual key: %v", err)

The rest of the middleware (MCP tool header injection and provider load‑balancing) looks reasonable and safely falls back to next(ctx) on errors.

Also applies to: 263-314

framework/tracing/store.go (1)

13-22: Drop unused Tracer and RequestID fields from DeferredSpanInfo

DeferredSpanInfo.Tracer and .RequestID are not read or written anywhere in this file; only SpanID, StartTime, FirstChunkTime, AccumulatedChunks, and mu are used. Keeping dead fields here increases memory footprint and obscures the actual responsibilities of the struct.

Unless there is concrete usage elsewhere in the stack, consider removing these fields and updating the comment to match.

plugins/logging/main.go (1)

225-233: Verify that BifrostContextKeyTracer is actually present during PreHook for streaming

PreHook creates a stream accumulator only when both tracer and trace ID are found in the context:

if bifrost.IsStreamRequestType(req.RequestType) {
    if tracer, ok := ctx.Value(schemas.BifrostContextKeyTracer).(schemas.Tracer); ok && tracer != nil {
        if traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string); ok && traceID != "" {
            tracer.CreateStreamAccumulator(traceID, createdTimestamp)
        }
    }
}

From core/bifrost.go, BifrostContextKeyTracer is set inside executeRequestWithRetries, which runs after plugin PreHooks. Unless some upstream layer injects the tracer into the context earlier, this block will never run on the standard HTTP → Bifrost path, and ProcessStreamingChunk will operate without a CreateStreamAccumulator having been called (or will rely on lazy creation inside the tracer).

Either:

  • Move accumulator initialization into the tracer (e.g. inside ProcessStreamingChunk if missing), or
  • Ensure BifrostContextKeyTracer (and trace ID) are set before RunPreHooks, or
  • Remove this PreHook branch if the tracer now owns accumulator lifecycle.

Also applies to: 423-438

plugins/otel/converter.go (1)

148-170: Avoid unsafe uint64int64 cast in anyToKeyValue

The uint64 branch casts directly to int64:

case uint64:
    return kvInt(key, int64(v))

For values > math.MaxInt64, this overflows and will produce a negative or otherwise invalid value in OTEL attributes.

A simple guard (clamp or log-and-cap) would make this safe.

Proposed fix
 import (
 	"encoding/hex"
 	"fmt"
 	"strings"
+	"math"
@@
 	case uint32:
 		return kvInt(key, int64(v))
 	case uint64:
-		return kvInt(key, int64(v))
+		if v > math.MaxInt64 {
+			// Clamp to MaxInt64 to avoid overflow
+			return kvInt(key, math.MaxInt64)
+		}
+		return kvInt(key, int64(v))
transports/bifrost-http/handlers/middlewares.go (1)

201-279: TracingMiddleware should call StartSpan with context.Context, not *fasthttp.RequestCtx

TracingMiddleware.Middleware currently does:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

where ctx is *fasthttp.RequestCtx. However, the tracer implementation used elsewhere in the stack implements core/schemas.Tracer, whose StartSpan expects a context.Context. *fasthttp.RequestCtx does not implement context.Context, so this will not type-check unless the tracer has a different signature here.

You likely want to:

  • Build a proper context.Context (e.g. baseCtx := context.Background() or one derived from the request/trace ID), and
  • Pass that to StartSpan, then
  • Use the returned spanCtx only for span propagation (e.g. extracting BifrostContextKeySpanID as you already do).

For example:

baseCtx := context.Background()
spanCtx, rootSpan := m.tracer.Load().StartSpan(baseCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

and continue to read BifrostContextKeySpanID from spanCtx as you do today.

core/schemas/trace.go (1)

40-49: Protect Trace.Reset, Span.End, and Span.Reset with the existing mutexes to avoid data races.

Trace and Span both have mu sync.Mutex, and mutation helpers (AddSpan, SetAttribute, AddEvent) correctly lock it. However:

  • Trace.Reset() clears RootSpan, Spans, and timing/attribute fields without acquiring t.mu.
  • Span.End() updates EndTime, Status, and StatusMsg without acquiring s.mu.
  • Span.Reset() clears all span fields, including Events, without acquiring s.mu.

If traces/spans are reused from pools or accessed concurrently (e.g., via the tracer and plugins) while these methods run, Go’s race detector will flag data races and behavior becomes nondeterministic.

Consider wrapping these mutating methods in the same mutex used elsewhere, e.g.:

 func (t *Trace) Reset() {
-	t.TraceID = ""
-	t.ParentID = ""
-	t.RootSpan = nil
-	t.Spans = t.Spans[:0]
-	t.StartTime = time.Time{}
-	t.EndTime = time.Time{}
-	t.Attributes = nil
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.TraceID = ""
+	t.ParentID = ""
+	t.RootSpan = nil
+	t.Spans = t.Spans[:0]
+	t.StartTime = time.Time{}
+	t.EndTime = time.Time{}
+	t.Attributes = nil
 }
 
 func (s *Span) End(status SpanStatus, statusMsg string) {
-	s.EndTime = time.Now()
-	s.Status = status
-	s.StatusMsg = statusMsg
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.EndTime = time.Now()
+	s.Status = status
+	s.StatusMsg = statusMsg
 }
 
 func (s *Span) Reset() {
-	s.SpanID = ""
-	s.ParentID = ""
-	s.TraceID = ""
-	s.Name = ""
-	s.Kind = SpanKindUnspecified
-	s.StartTime = time.Time{}
-	s.EndTime = time.Time{}
-	s.Status = SpanStatusUnset
-	s.StatusMsg = ""
-	s.Attributes = nil
-	s.Events = s.Events[:0]
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.SpanID = ""
+	s.ParentID = ""
+	s.TraceID = ""
+	s.Name = ""
+	s.Kind = SpanKindUnspecified
+	s.StartTime = time.Time{}
+	s.EndTime = time.Time{}
+	s.Status = SpanStatusUnset
+	s.StatusMsg = ""
+	s.Attributes = nil
+	s.Events = s.Events[:0]
 }

This matches the locking strategy already used by the other methods and keeps reuse via pools safe.

Also applies to: 84-104

framework/tracing/tracer.go (1)

233-258: Make buildCompleteResponseFromChunks deterministic by sorting choice and tool‑call indices.

The reconstruction logic still builds indices from maps but never sorts them:

  • choiceIndices is populated from choiceMap (map[int]*choiceAccumulator) and iterated in map order.
  • tcIndices is populated from acc.toolCalls (map[int]ChatAssistantMessageToolCall) and likewise iterated in map order.

Despite comments about “Sort choice indices for deterministic output” and “Sort tool calls by index”, no sort.Ints(...) calls are present. This means the order of Choices and ToolCalls in the final ChatResponse is nondeterministic, which can make logs and tests flaky.

Consider:

	import "sort"
	...
	choiceIndices := make([]int, 0, len(choiceMap))
	for idx := range choiceMap {
		choiceIndices = append(choiceIndices, idx)
	}
-	for _, idx := range choiceIndices {
+	sort.Ints(choiceIndices)
+	for _, idx := range choiceIndices {
		...
		if len(accum.toolCalls) > 0 {
-			tcIndices := make([]int, 0, len(accum.toolCalls))
-			for tcIdx := range accum.toolCalls {
-				tcIndices = append(tcIndices, tcIdx)
-			}
+			tcIndices := make([]int, 0, len(accum.toolCalls))
+			for tcIdx := range accum.toolCalls {
+				tcIndices = append(tcIndices, tcIdx)
+			}
+			sort.Ints(tcIndices)
			toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
			for _, tcIdx := range tcIndices {
				toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
			}

This preserves existing behavior while making the output stable across runs.

Also applies to: 271-293, 393-455

framework/tracing/llmspan.go (1)

190-193: ExtraParams written directly to attrs map may cause attribute key collisions.

User-provided keys from ExtraParams are written directly to the attributes map without namespacing. If a user sets a key like "gen_ai.request.model" in ExtraParams, it would overwrite the standard attribute set on line 22.

This pattern repeats throughout the file at lines 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, and 1027-1029.

🧹 Nitpick comments (12)
.github/workflows/scripts/push-mintlify-changelog.sh (2)

239-244: Branch resolution logic is sound for CI environments.

The dynamic branch detection with detached HEAD handling is appropriate. The fallback chain (current branch → GITHUB_REF_NAMEmain) correctly handles common CI scenarios where the repository is checked out in detached HEAD state.

💡 Optional: Add explanatory comment

For future maintainers, consider adding a brief comment:

 # Pulling again before committing
+# Determine current branch; in CI detached HEAD scenarios, use GITHUB_REF_NAME
 CURRENT_BRANCH="$(git rev-parse --abbrev-ref HEAD)"
 if [ "$CURRENT_BRANCH" = "HEAD" ]; then
   # In detached HEAD state (common in CI), use GITHUB_REF_NAME or default to main
   CURRENT_BRANCH="${GITHUB_REF_NAME:-main}"
 fi

244-255: Add error handling for git operations.

The script lacks error handling for critical git operations (pull, commit, push). If any of these commands fail, the script continues silently, which can lead to incomplete changelog deployments in CI/CD workflows.

🔎 Recommended improvements

Option 1: Enable strict error handling at script level

Add at the top of the script (after the shebang):

 #!/usr/bin/env bash
+set -e  # Exit on error
+set -o pipefail  # Exit on pipe failure

Option 2: Add explicit error checking for critical operations

-git pull origin "$CURRENT_BRANCH"
+if ! git pull origin "$CURRENT_BRANCH"; then
+  echo "❌ Failed to pull from $CURRENT_BRANCH"
+  exit 1
+fi
+
 # Commit and push changes
 git add docs/changelogs/$VERSION.mdx
 git add docs/docs.json
 # Add all cleaned changelog files
 for file in "${CLEANED_CHANGELOG_FILES[@]}"; do
   git add "$file"
 done
 git config user.name "github-actions[bot]"
 git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
-git commit -m "Adds changelog for $VERSION --skip-pipeline"
-git push origin "$CURRENT_BRANCH"
+if git commit -m "Adds changelog for $VERSION --skip-pipeline"; then
+  if ! git push origin "$CURRENT_BRANCH"; then
+    echo "❌ Failed to push to $CURRENT_BRANCH"
+    exit 1
+  fi
+  echo "✅ Successfully pushed changelog for $VERSION"
+else
+  echo "⚠️  No changes to commit"
+fi
ui/components/devProfiler.tsx (1)

139-145: Consider differentiating the minimize button icon.

Both the "Collapse" toggle (Line 134) and "Minimize" button (Line 144) use <ChevronDown> when expanded, which may confuse users about their different behaviors. Consider using a distinct icon for minimize (e.g., Minus from lucide-react).

ui/lib/store/apis/devApi.ts (1)

59-59: Nitpick: Comment is slightly misleading.

The comment "polls every 10 seconds" describes consumer behavior, not endpoint behavior. The polling interval is configured at the call site in devProfiler.tsx, not in this endpoint definition.

🔎 Suggested clarification
   endpoints: (builder) => ({
-    // Get dev pprof data - polls every 10 seconds
+    // Get dev pprof data (consumers typically poll this endpoint)
     getDevPprof: builder.query<PprofData, void>({
framework/plugins/dynamicplugin.go (1)

24-28: Dynamic plugin now correctly treats HTTPTransportMiddleware as a factory; only error text is slightly stale.

The change to store a func() schemas.BifrostHTTPMiddleware and assert the symbol to that type fixes the earlier runtime type‑assertion issue when loading plugins that export a factory. The accessor HTTPTransportMiddleware() then correctly returns the constructed middleware.

As a minor polish, the error message at line 146 still mentions func(next fasthttp.RequestHandler) fasthttp.RequestHandler; updating it to reflect func() schemas.BifrostHTTPMiddleware would make debugging clearer.

Also applies to: 36-39, 140-147

plugins/logging/main.go (1)

96-111: Make Cleanup idempotent to avoid double-close panics on done

Cleanup stops the ticker and unconditionally closes p.done. If Cleanup is ever called twice for the same LoggerPlugin instance (e.g., via tests or misuse), the second close(p.done) will panic.

Wrapping the shutdown path in a sync.Once keeps the API safer without changing normal behavior.

Proposed diff
 type LoggerPlugin struct {
 	ctx                   context.Context
 	store                 logstore.LogStore
 	disableContentLogging *bool
 	pricingManager        *modelcatalog.ModelCatalog
 	mu                    sync.Mutex
 	done                  chan struct{}
 	wg                    sync.WaitGroup
+	cleanupOnce           sync.Once
 	logger                schemas.Logger
 	logCallback           LogCallback
 	droppedRequests       atomic.Int64
 	cleanupTicker         *time.Ticker // Ticker for cleaning up old processing logs
 	logMsgPool            sync.Pool    // Pool for reusing LogMessage structs
 	updateDataPool        sync.Pool    // Pool for reusing UpdateLogData structs
@@
 func (p *LoggerPlugin) Cleanup() error {
-	// Stop the cleanup ticker
-	if p.cleanupTicker != nil {
-		p.cleanupTicker.Stop()
-	}
-	// Signal the background worker to stop
-	close(p.done)
-	// Wait for the background worker to finish processing remaining items
-	p.wg.Wait()
-	// Note: Accumulator cleanup is handled by the tracer, not the logging plugin
-	// GORM handles connection cleanup automatically
-	return nil
+	p.cleanupOnce.Do(func() {
+		// Stop the cleanup ticker
+		if p.cleanupTicker != nil {
+			p.cleanupTicker.Stop()
+		}
+		// Signal the background worker to stop
+		close(p.done)
+		// Wait for the background worker to finish processing remaining items
+		p.wg.Wait()
+		// Note: Accumulator cleanup is handled by the tracer, not the logging plugin
+		// GORM handles connection cleanup automatically
+	})
+	return nil
 }

Also applies to: 144-156, 158-180, 614-625

framework/tracing/store.go (1)

341-360: Be explicit that TTL cleanup may reclaim still-active traces

cleanupOldTraces deletes any trace whose StartTime is older than now - ttl, regardless of whether it’s still in active use:

cutoff := time.Now().Add(-s.ttl)
s.traces.Range(func(key, value any) bool {
    trace := value.(*schemas.Trace)
    if trace.StartTime.Before(cutoff) {
        if deleted, ok := s.traces.LoadAndDelete(key); ok {
            s.ReleaseTrace(deleted.(*schemas.Trace))
            count++
        }
    }
    return true
})

If a very long-lived request exceeds ttl, its trace could be reset and returned to the pool while spans are still being added, leading to missing telemetry or potential data races.

At minimum, this should be clearly documented and ttl configured well above any realistic request duration. Alternatively, consider tightening the predicate (e.g., only cleaning traces that have been explicitly completed or have an EndTime set).

transports/bifrost-http/lib/config.go (2)

276-356: LoadConfig signature and default/file paths look consistent with the new API.

The removal of EnterpriseOverrides and the updated calls to loadConfigFromDefaults and loadConfigFromFile are internally consistent, and LoadConfig now has a simpler surface that matches how Bootstrap uses it. Just ensure any other call sites in the stack have been updated to the new (ctx, configDirPath) signature.


1509-1542: Pricing manager initialization now uniformly uses default modelcatalog.Init (no overrides).

Both initFrameworkConfigFromFile and initDefaultFrameworkConfig initialize pricingConfig and call modelcatalog.Init(ctx, pricingConfig, config.ConfigStore, nil, logger), which aligns with the removal of enterprise overrides and with LoadPricingManager in the server. This keeps pricing behavior consistent across file-based and default config paths; the warning‑only handling of init errors is unchanged and acceptable given pricing is ancillary to core routing.

Also applies to: 1862-1915

transports/bifrost-http/server/server.go (1)

214-324: Plugin loading, middleware type migration, tracing, and dev‑pprof lifecycle wiring look consistent.

  • LoadPlugin/LoadPlugins correctly use generics to return either concrete plugin types or schemas.Plugin, and built‑in cases (telemetry, logging, governance, maxim, semanticcache, otel) are wired through a single helper without EnterpriseOverrides.
  • All HTTP middleware signatures have been migrated to schemas.BifrostHTTPMiddleware (in RegisterInferenceRoutes, RegisterAPIRoutes, RegisterUIRoutes, and PrepareCommonMiddlewares), aligning server wiring with the new plugin v2 middleware type.
  • Bootstrap now uses lib.LoadConfig(ctx, configDir) and LoadPlugins(ctx, s.Config), then initializes tracing only when observability/logging plugins are present, injecting TracingMiddleware ahead of the inference middlewares and setting the tracer on the client.
  • The dev pprof handler is now stored on the server, registered only in dev mode, and cleaned up during shutdown, which fixes the earlier goroutine/lifecycle leak.

These changes are cohesive with the stated PR goal of a unified HTTP middleware pattern plus improved observability, with no additional correctness issues in the shown paths.

Also applies to: 327-443, 942-1042, 1084-1096, 1110-1163, 1220-1253, 1299-1333

framework/tracing/tracer.go (1)

13-52: Overall Tracer implementation and streaming integration look solid and align with the new tracing architecture.

  • Tracer cleanly wraps TraceStore and delegates trace/span lifecycle operations (CreateTrace, EndTrace, ReleaseTrace, StartSpan, EndSpan, attribute/event setters) via the spanHandle abstraction, matching the schemas.Tracer interface (enforced by the compile‑time assertion).
  • LLM request/response/error attribute population uses helper functions from llmspan.go, keeping schema logic centralized while keeping tracer lightweight.
  • Deferred span support (StoreDeferredSpan, GetDeferredSpanHandle, ClearDeferredSpan, GetDeferredSpanID) correctly keys by traceID and pairs with the store’s deferred span map.
  • Streaming integration via the embedded streaming.Accumulator (CreateStreamAccumulator, CleanupStreamAccumulator, ProcessStreamingChunk, GetAccumulator) is well‑designed: ProcessStreamingChunk wraps the original BifrostContext to inject traceID as requestID and preserve the stream end indicator, then maps accumulator results into schemas.StreamAccumulatorResult for plugins.

Aside from the accumulator cleanup and deterministic ordering concerns noted separately, this file is a good fit for the new tracing+plugins v2 stack.

Also applies to: 61-171, 172-231, 460-531

framework/tracing/llmspan.go (1)

1279-1283: Consider using strings.Builder for string concatenation in loops.

For consistency with line 1315 (which already uses strings.Builder), consider refactoring the string concatenation loops at lines 1279-1283 and 1289-1292 to use strings.Builder as well.

While the performance impact is likely minimal for typical workloads (small numbers of blocks), using strings.Builder throughout provides consistency and better performance characteristics for edge cases with many blocks.

🔎 Example refactor for lines 1279-1283
 		} else if msg.Content.ContentBlocks != nil {
+			var builder strings.Builder
 			for _, block := range msg.Content.ContentBlocks {
 				if block.Text != nil {
-					content += *block.Text
+					builder.WriteString(*block.Text)
 				}
 			}
+			content = builder.String()
 		}

Also applies to: 1289-1292

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e8ca91d and 5186ad3.

⛔ Files ignored due to path filters (3)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (100)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • .github/workflows/scripts/release-framework.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/go.mod
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • framework/configstore/tables/mcp.go
  • core/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (3)
  • plugins/semanticcache/version
  • plugins/otel/version
  • plugins/maxim/version
🚧 Files skipped from review as they are similar to previous changes (35)
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/mcp.go
  • ui/components/ui/switch.tsx
  • plugins/telemetry/version
  • framework/streaming/audio.go
  • plugins/governance/version
  • core/bifrost_test.go
  • .github/workflows/scripts/release-bifrost-http.sh
  • plugins/mocker/version
  • framework/version
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/health.go
  • ui/app/clientLayout.tsx
  • plugins/logging/utils.go
  • transports/version
  • transports/bifrost-http/handlers/session.go
  • core/schemas/context.go
  • transports/bifrost-http/handlers/plugins.go
  • framework/streaming/chat.go
  • examples/plugins/hello-world/main.go
  • plugins/jsonparser/main.go
  • framework/streaming/types.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/middlewares_test.go
  • examples/plugins/hello-world/go.mod
  • framework/streaming/transcription.go
  • transports/bifrost-http/lib/config_test.go
  • core/version
  • plugins/logging/version
  • core/schemas/bifrost.go
  • transports/bifrost-http/lib/middleware.go
  • docs/docs.json
  • transports/bifrost-http/handlers/integrations.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • docs/plugins/migration-guide.mdx
  • ui/lib/store/apis/index.ts
  • ui/components/sidebar.tsx
  • transports/bifrost-http/handlers/ui.go
  • plugins/maxim/main.go
  • core/schemas/plugin.go
  • core/changelog.md
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/governance/main.go
  • transports/bifrost-http/integrations/router.go
  • framework/plugins/dynamicplugin_test.go
  • core/providers/utils/utils.go
  • ui/components/devProfiler.tsx
  • plugins/semanticcache/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • plugins/otel/main.go
  • plugins/governance/changelog.md
  • ui/lib/store/apis/devApi.ts
  • plugins/semanticcache/changelog.md
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/providers.go
  • plugins/jsonparser/changelog.md
  • framework/tracing/store.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/maxim/changelog.md
  • framework/tracing/propagation.go
  • core/bifrost.go
  • plugins/otel/converter.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/handlers/middlewares.go
  • core/schemas/trace.go
  • transports/go.mod
  • ui/components/ui/sheet.tsx
  • transports/bifrost-http/server/server.go
  • plugins/mocker/changelog.md
  • docs/plugins/getting-started.mdx
  • framework/streaming/responses.go
  • plugins/logging/changelog.md
  • transports/changelog.md
  • core/schemas/tracer.go
  • framework/tracing/tracer.go
  • plugins/logging/main.go
  • plugins/otel/changelog.md
  • docs/plugins/writing-plugin.mdx
  • transports/bifrost-http/handlers/devpprof_windows.go
  • framework/changelog.md
  • transports/bifrost-http/handlers/config.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/websocket.go
  • plugins/jsonparser/version
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/ui.go
  • plugins/maxim/main.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/handlers/mcpserver.go
  • plugins/governance/main.go
  • transports/bifrost-http/integrations/router.go
  • framework/plugins/dynamicplugin_test.go
  • core/providers/utils/utils.go
  • plugins/semanticcache/main.go
  • transports/bifrost-http/handlers/devpprof.go
  • plugins/otel/main.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/providers.go
  • framework/tracing/store.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/inference.go
  • framework/tracing/propagation.go
  • core/bifrost.go
  • plugins/otel/converter.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/handlers/middlewares.go
  • core/schemas/trace.go
  • transports/bifrost-http/server/server.go
  • framework/streaming/responses.go
  • core/schemas/tracer.go
  • framework/tracing/tracer.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/config.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/websocket.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/websocket.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
🧬 Code graph analysis (28)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/maxim/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/handlers/logging.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (2)
  • GetName (15-17)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/mcpserver.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/integrations/router.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (6)
  • ResponsesRequest (97-97)
  • EmbeddingRequest (99-99)
  • SpeechRequest (100-100)
  • TranscriptionRequest (102-102)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
transports/bifrost-http/handlers/inference.go (5)
  • ChatRequest (172-176)
  • ResponsesRequest (266-270)
  • EmbeddingRequest (273-277)
  • SpeechRequest (279-283)
  • TranscriptionRequest (285-289)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
ui/components/devProfiler.tsx (1)
ui/lib/utils/port.ts (1)
  • isDevelopmentMode (110-112)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
transports/bifrost-http/handlers/devpprof.go (3)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/handlers/utils.go (1)
  • SendJSON (16-22)
plugins/otel/main.go (5)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
core/schemas/context.go (1)
  • BifrostContext (32-42)
core/schemas/bifrost.go (3)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostError (474-483)
core/schemas/trace.go (1)
  • Trace (10-19)
plugins/telemetry/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/providers.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/store.go (2)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (147-147)
  • BifrostContextKeyTraceCompleter (148-148)
core/bifrost.go (5)
core/schemas/tracer.go (1)
  • DefaultTracer (181-183)
core/schemas/trace.go (12)
  • SpanKindFallback (129-129)
  • AttrProviderName (159-159)
  • AttrRequestModel (160-160)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindPlugin (123-123)
  • AttrPluginInvocations (202-202)
  • AttrPluginAvgDurationMs (203-203)
  • AttrPluginTotalDurationMs (204-204)
  • AttrPluginErrorCount (205-205)
framework/tracing/helpers.go (1)
  • EndSpan (50-56)
core/utils.go (1)
  • IsStreamRequestType (197-199)
core/schemas/context.go (1)
  • NewBifrostContextWithTimeout (70-73)
plugins/otel/converter.go (2)
plugins/otel/main.go (1)
  • OtelPlugin (58-75)
core/schemas/trace.go (17)
  • Trace (10-19)
  • Span (52-65)
  • SpanKind (115-115)
  • SpanKindLLMCall (121-121)
  • SpanKindHTTPRequest (131-131)
  • SpanKindPlugin (123-123)
  • SpanKindInternal (139-139)
  • SpanKindRetry (127-127)
  • SpanKindFallback (129-129)
  • SpanKindMCPTool (125-125)
  • SpanKindEmbedding (133-133)
  • SpanKindSpeech (135-135)
  • SpanKindTranscription (137-137)
  • SpanStatus (143-143)
  • SpanStatusOk (149-149)
  • SpanStatusError (151-151)
  • SpanEvent (107-111)
framework/tracing/helpers.go (3)
core/schemas/bifrost.go (1)
  • BifrostContextKeyTraceID (143-143)
framework/tracing/store.go (1)
  • TraceStore (25-37)
core/schemas/trace.go (5)
  • Trace (10-19)
  • SpanKind (115-115)
  • Span (52-65)
  • SpanStatus (143-143)
  • SpanEvent (107-111)
transports/bifrost-http/handlers/middlewares.go (5)
core/schemas/plugin.go (3)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/bifrost.go (4)
  • BifrostContextKeyTraceID (143-143)
  • BifrostContextKeyTraceCompleter (148-148)
  • BifrostContextKeySpanID (144-144)
  • BifrostContextKeyDeferTraceCompletion (147-147)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (161-161)
framework/streaming/responses.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
core/schemas/tracer.go (6)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (845-852)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (2)
  • SpanKind (115-115)
  • SpanStatus (143-143)
core/schemas/bifrost.go (2)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
core/schemas/context.go (1)
  • BifrostContext (32-42)
framework/tracing/tracer.go (9)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (1)
  • StreamAccumulatorResult (15-33)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (5)
  • BifrostContextKeySpanID (144-144)
  • BifrostRequest (173-193)
  • BifrostResponse (335-355)
  • BifrostContextKeyRequestID (124-124)
  • BifrostContextKeyStreamEndIndicator (131-131)
framework/tracing/llmspan.go (3)
  • PopulateRequestAttributes (14-60)
  • PopulateResponseAttributes (64-106)
  • PopulateErrorAttributes (109-124)
core/schemas/chatcompletions.go (6)
  • BifrostResponseChoice (736-744)
  • ChatReasoningDetails (755-762)
  • ChatAssistantMessageToolCall (710-716)
  • ChatStreamResponseChoice (783-785)
  • ChatAssistantMessage (649-656)
  • ChatNonStreamResponseChoice (777-780)
core/schemas/context.go (2)
  • BifrostContext (32-42)
  • NewBifrostContext (47-65)
framework/streaming/types.go (1)
  • StreamResponseTypeFinal (24-24)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/websocket.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

16-16: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from 5186ad3 to b12158e Compare December 25, 2025 14:25
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
transports/bifrost-http/server/server.go (1)

203-212: Avoid returning Config.Providers map outside the lock (concurrent map access risk).

GovernanceInMemoryStore.GetConfiguredProviders returns s.Config.Providers while only briefly holding Config.Mu.RLock. Callers keep a reference to the underlying map after the lock is released, but writers (e.g., AddProvider/UpdateProviderConfig/RemoveProvider) mutate the same map under Config.Mu. Concurrent map read/write will panic at runtime.

Return a copy instead of the shared map, e.g.:

Proposed fix
 func (s *GovernanceInMemoryStore) GetConfiguredProviders() map[schemas.ModelProvider]configstore.ProviderConfig {
-	// Use read lock for thread-safe access - no need to copy on hot path
-	s.Config.Mu.RLock()
-	defer s.Config.Mu.RUnlock()
-	return s.Config.Providers
+	s.Config.Mu.RLock()
+	defer s.Config.Mu.RUnlock()
+
+	out := make(map[schemas.ModelProvider]configstore.ProviderConfig, len(s.Config.Providers))
+	for k, v := range s.Config.Providers {
+		out[k] = v
+	}
+	return out
 }
transports/bifrost-http/lib/config.go (1)

1509-1527: Guard against nil PricingSyncInterval in initFrameworkConfigFromFile.

When loading from file and no framework config exists in the store, you do:

if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
    pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
    syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
    pricingConfig.PricingSyncInterval = &syncDuration
}

If pricing.pricing_sync_interval is omitted from config.json, PricingSyncInterval will be nil and this panics at startup.

Align with the safer pattern you already use in initDefaultFrameworkConfig by checking for nil (and optionally >0) before dereferencing, e.g.:

Proposed fix
-} else if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
-    pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
-    syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
-    pricingConfig.PricingSyncInterval = &syncDuration
-}
+} else if cfg := configData.FrameworkConfig; cfg != nil && cfg.Pricing != nil {
+    pricingConfig.PricingURL = cfg.Pricing.PricingURL
+    if cfg.Pricing.PricingSyncInterval != nil && *cfg.Pricing.PricingSyncInterval > 0 {
+        syncDuration := time.Duration(*cfg.Pricing.PricingSyncInterval) * time.Second
+        pricingConfig.PricingSyncInterval = &syncDuration
+    }
+}
♻️ Duplicate comments (16)
plugins/logging/version (1)

1-1: Version bump inconsistent with breaking changes in this PR stack.

The patch-level bump to 1.3.53 does not reflect the breaking change introduced by the HTTPTransportMiddleware migration. Given that:

  • The transports package bumped to 1.4.0-prerelease1
  • This PR targets the v1.4.0 branch
  • Plugins implementing TransportInterceptor must be rewritten to use HTTPTransportMiddleware

Bump to 1.4.0 or 1.4.0-prerelease1 to signal the breaking change and maintain version consistency across the stack.

ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

183-186: Add aria-label for accessibility.

The icon-only button lacks an accessible name for screen readers. Add an aria-label to describe the button's purpose.

🔎 Proposed fix
 <DropdownMenuTrigger asChild>
-  <Button variant="ghost" size="icon">
+  <Button variant="ghost" size="icon" aria-label="Open actions menu">
     <MoreVertical className="h-3 w-3" />
   </Button>
 </DropdownMenuTrigger>
plugins/otel/changelog.md (1)

1-20: Migration guide link requires verification.

The changelog references /docs/plugins/migration-guide on line 19. This has been flagged in previous reviews—ensure the migration guide exists at the referenced path or update the link to point to the correct documentation location.

core/schemas/trace.go (3)

40-49: Critical concurrency issue: Trace.Reset() still lacks mutex protection.

This method modifies all fields including the Spans slice without holding the mutex, creating a data race if called concurrently with AddSpan or GetSpan. This issue was previously flagged but remains unresolved.

🔎 Proposed fix
 // Reset clears the trace for reuse from pool
 func (t *Trace) Reset() {
+	t.mu.Lock()
+	defer t.mu.Unlock()
 	t.TraceID = ""
 	t.ParentID = ""
 	t.RootSpan = nil
 	t.Spans = t.Spans[:0]
 	t.StartTime = time.Time{}
 	t.EndTime = time.Time{}
 	t.Attributes = nil
 }

84-89: Critical concurrency issue: Span.End() lacks mutex protection.

Unlike SetAttribute and AddEvent, the End method modifies EndTime, Status, and StatusMsg without holding the mutex. This creates a data race if called concurrently with other span operations. This issue was previously flagged but remains unresolved.

🔎 Proposed fix
 // End marks the span as complete with the given status
 func (s *Span) End(status SpanStatus, statusMsg string) {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.EndTime = time.Now()
 	s.Status = status
 	s.StatusMsg = statusMsg
 }

91-104: Critical concurrency issue: Span.Reset() lacks mutex protection.

Similar to Trace.Reset(), this method modifies fields without acquiring the mutex, creating a data race when a span is reused from a pool while another goroutine holds a reference. This issue was previously flagged but remains unresolved.

🔎 Proposed fix
 // Reset clears the span for reuse from pool
 func (s *Span) Reset() {
+	s.mu.Lock()
+	defer s.mu.Unlock()
 	s.SpanID = ""
 	s.ParentID = ""
 	s.TraceID = ""
 	s.Name = ""
 	s.Kind = SpanKindUnspecified
 	s.StartTime = time.Time{}
 	s.EndTime = time.Time{}
 	s.Status = SpanStatusUnset
 	s.StatusMsg = ""
 	s.Attributes = nil
 	s.Events = s.Events[:0]
 }
examples/plugins/hello-world/main.go (1)

19-27: Example demonstrates broken data flow between middleware and hooks.

The middleware sets a value on fasthttp.RequestCtx (line 23) but PreHook and PostHook (lines 30, 39-40) attempt to read from *schemas.BifrostContext. These are different context types, so the value will always be nil.

Either remove the context value reads from the hooks (since the propagation doesn't work), or add a comment explaining that middleware operates on the HTTP transport context while hooks operate on the Bifrost internal context.

🔎 Suggested fix to remove broken context reads
 func PreHook(ctx *schemas.BifrostContext, req *schemas.BifrostRequest) (*schemas.BifrostRequest, *schemas.PluginShortCircuit, error) {
-	value1 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-transport-interceptor"))
-	fmt.Println("value1:", value1)
 	ctx.SetValue(schemas.BifrostContextKey("hello-world-plugin-pre-hook"), "pre-hook-value")
 	fmt.Println("PreHook called")
 	return req, nil, nil
 }

 func PostHook(ctx *schemas.BifrostContext, resp *schemas.BifrostResponse, bifrostErr *schemas.BifrostError) (*schemas.BifrostResponse, *schemas.BifrostError, error) {
 	fmt.Println("PostHook called")
-	value1 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-transport-interceptor"))
-	fmt.Println("value1:", value1)
 	value2 := ctx.Value(schemas.BifrostContextKey("hello-world-plugin-pre-hook"))
 	fmt.Println("value2:", value2)
 	return resp, bifrostErr, nil
 }
framework/changelog.md (1)

1-1: Fix grammar and expand tracing framework description.

Line 1 contains a grammar error: "to enabling e2e tracing" should be "to enable e2e tracing".

Additionally, consider briefly explaining what the tracing framework provides (e.g., "enables plugins to report spans and traces for distributed tracing integration") to match the detail level of the HTTPTransportMiddleware breaking change documentation below.

🔎 Suggested fix
-- feat: adds new tracing framework for allowing plugins to enable e2e tracing
+- feat: adds new tracing framework to enable e2e tracing. The framework allows plugins to report spans and traces for distributed tracing integration with observability backends.
docs/plugins/migration-guide.mdx (1)

247-250: Non-existent version tag will break user migration.

Line 249 references @v1.4.0 which doesn't exist yet (this PR is open and targets the v1.4.0 branch). Users following this guide will encounter an error.

Before merging, either:

  1. Create and push the v1.4.0 tag, or
  2. Update to reference an existing version/commit, or
  3. Add a note: "Replace @v1.4.0 with the latest release version after v1.4.0 is published"
🔎 Suggested fix with placeholder
 2. **Update Bifrost to v1.4.x:**
    ```bash
-   go get github.com/maximhq/bifrost/[email protected]
+   # Replace @v1.4.0 with the actual release version once published
+   go get github.com/maximhq/bifrost/[email protected]
    ```

Or reference the target branch until release:

-   go get github.com/maximhq/bifrost/[email protected]
+   go get github.com/maximhq/bifrost/core@latest
transports/bifrost-http/integrations/router.go (1)

502-693: Non-streaming requests use wrong context - breaks tracing and plugin propagation.

All non-streaming Bifrost client calls (lines 514, 534, 557, 580, 603, 626, 660) pass ctx (*fasthttp.RequestCtx) instead of the converted Bifrost context (*bifrostCtx). The comment at line 503 acknowledges that the "cancellable context from ConvertToBifrostContext" should be used, but the code doesn't follow this.

This breaks:

  • Tracing (all BifrostContextKey* values are on bifrostCtx, not ctx)
  • Plugin context propagation
  • Timeout and cancellation behavior
🔎 Proposed fix

Apply this pattern to all non-streaming calls:

 	case bifrostReq.ListModelsRequest != nil:
-		listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, bifrostReq.ListModelsRequest)
+		listModelsResponse, bifrostErr := g.client.ListModelsRequest(*bifrostCtx, bifrostReq.ListModelsRequest)
 
 	case bifrostReq.TextCompletionRequest != nil:
-		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, bifrostReq.TextCompletionRequest)
+		textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(*bifrostCtx, bifrostReq.TextCompletionRequest)
 
 	case bifrostReq.ChatRequest != nil:
-		chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, bifrostReq.ChatRequest)
+		chatResponse, bifrostErr := g.client.ChatCompletionRequest(*bifrostCtx, bifrostReq.ChatRequest)
 
 	case bifrostReq.ResponsesRequest != nil:
-		responsesResponse, bifrostErr := g.client.ResponsesRequest(ctx, bifrostReq.ResponsesRequest)
+		responsesResponse, bifrostErr := g.client.ResponsesRequest(*bifrostCtx, bifrostReq.ResponsesRequest)
 
 	case bifrostReq.EmbeddingRequest != nil:
-		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(ctx, bifrostReq.EmbeddingRequest)
+		embeddingResponse, bifrostErr := g.client.EmbeddingRequest(*bifrostCtx, bifrostReq.EmbeddingRequest)
 
 	case bifrostReq.SpeechRequest != nil:
-		speechResponse, bifrostErr := g.client.SpeechRequest(ctx, bifrostReq.SpeechRequest)
+		speechResponse, bifrostErr := g.client.SpeechRequest(*bifrostCtx, bifrostReq.SpeechRequest)
 
 	case bifrostReq.TranscriptionRequest != nil:
-		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(ctx, bifrostReq.TranscriptionRequest)
+		transcriptionResponse, bifrostErr := g.client.TranscriptionRequest(*bifrostCtx, bifrostReq.TranscriptionRequest)
core/providers/utils/utils.go (1)

1424-1513: Pass context by value, not by pointer (Go convention).

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. Contexts should always be passed by value, never by pointer. The context.Context type is designed to be passed by value and is safe for concurrent use.

🔎 Proposed fix
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == nil || ctx == context.Background() || ctx == context.TODO() {
 		return
 	}
 
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)
 	if !ok || traceID == "" {
 		return
 	}
 
-	tracerVal := (*ctx).Value(schemas.BifrostContextKeyTracer)
+	tracerVal := ctx.Value(schemas.BifrostContextKeyTracer)
 	// ... continue pattern for all (*ctx).Value calls

And update all call sites (lines 901, 928, 952, 978):

-			completeDeferredSpan(&ctx, processedResponse, processedError)
+			completeDeferredSpan(ctx, processedResponse, processedError)
plugins/governance/main.go (1)

233-261: Add prefix validation for x-bf-vk header for consistency.

The x-bf-vk header on lines 236-238 is returned without validating that it starts with VirtualKeyPrefix, unlike the other headers (Authorization, x-api-key, x-goog-api-key) which all validate the prefix. This inconsistency could allow invalid virtual keys to pass through when provided via the x-bf-vk header.

🔎 Proposed fix
 	vkHeader := ctx.Request.Header.Peek("x-bf-vk")
 	if string(vkHeader) != "" {
-		return bifrost.Ptr(string(vkHeader))
+		vkValue := string(vkHeader)
+		if strings.HasPrefix(strings.ToLower(vkValue), VirtualKeyPrefix) {
+			return bifrost.Ptr(vkValue)
+		}
 	}
transports/bifrost-http/handlers/middlewares.go (1)

247-256: TracingMiddleware still passes *fasthttp.RequestCtx into StartSpan, which expects context.Context

StartSpan in core/schemas.Tracer takes a context.Context, but here you call:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

where ctx is *fasthttp.RequestCtx. This won’t type‑check and also prevents StartSpan from seeing the trace ID via ctx.Value(...).

You likely want to construct a proper context.Context that carries the W3C trace ID, for example:

goCtx := context.Background()
if v := ctx.UserValue(schemas.BifrostContextKeyTraceID); v != nil {
    if traceID, ok := v.(string); ok && traceID != "" {
        goCtx = context.WithValue(goCtx, schemas.BifrostContextKeyTraceID, traceID)
    }
}
spanCtx, rootSpan := m.tracer.Load().StartSpan(goCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

and then use spanCtx only for span propagation (e.g., to extract the span ID), keeping *fasthttp.RequestCtx for HTTP.

plugins/otel/converter.go (1)

148-171: Avoid unsafe uint64int64 cast in anyToKeyValue

The uint64 branch does:

case uint64:
    return kvInt(key, int64(v))

If v exceeds math.MaxInt64, this overflows and produces a negative OTEL attribute value. Even if unlikely, it makes debugging harder.

Consider clamping or preserving unsigned semantics, for example:

case uint64:
    if v > math.MaxInt64 {
        // optionally log a warning
        return kvInt(key, math.MaxInt64)
    }
    return kvInt(key, int64(v))
framework/tracing/llmspan.go (1)

191-193: Namespace ExtraParams keys to avoid clobbering core attributes.

Across the request helpers you write user‑supplied ExtraParams directly into attrs:

for k, v := range req.Params.ExtraParams {
    attrs[k] = fmt.Sprintf("%v", v)
}

This allows callers to overwrite standard attributes (e.g., gen_ai.request.model, gen_ai.provider) just by choosing the same key, which was already noted in a previous review.

Consider prefixing these keys (and doing it consistently in all helpers) to avoid collisions, e.g.:

Proposed pattern
- for k, v := range req.Params.ExtraParams {
-     attrs[k] = fmt.Sprintf("%v", v)
- }
+ for k, v := range req.Params.ExtraParams {
+     key := "gen_ai.extra." + k // or a similar, well-documented prefix
+     if _, exists := attrs[key]; !exists {
+         attrs[key] = fmt.Sprintf("%v", v)
+     }
+ }

Apply the same pattern to ExtraParams in chat, text, embedding, responses, batch, and file helpers.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 769-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029

framework/tracing/tracer.go (1)

393-455: Make buildCompleteResponseFromChunks deterministic by sorting indices.

The comments say “Sort choice indices for deterministic output” and “Sort tool calls by index”, but:

  • choiceIndices is built from a map and then iterated without sorting.
  • tcIndices is also built from a map and iterated without sorting.

Map iteration order is random, so choice and tool‑call ordering will vary between runs, which can make traces and logs flaky.

Add explicit sorts before the loops:

Proposed fix
 import (
 	"context"
 	"time"

 	"github.com/maximhq/bifrost/core/schemas"
 	"github.com/maximhq/bifrost/framework/modelcatalog"
 	"github.com/maximhq/bifrost/framework/streaming"
+	"sort"
 )

@@
 	// Build final choices from accumulated data
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)

@@
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
 					toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
 				}
 				msg.ChatAssistantMessage.ToolCalls = toolCalls

Also consider clarifying the function comment that this reconstruction currently only handles chat streaming responses (it bails out when lastChunk.ChatResponse == nil).

🧹 Nitpick comments (4)
transports/bifrost-http/lib/config_test.go (1)

6663-12271: LoadConfig signature updates across SQLite integration tests look correct

All updated call sites now use LoadConfig(ctx, tempDir) consistently, check err immediately, and ensure ConfigStore.Close(ctx) is invoked (either explicitly between loads or via defer on the final load). There’s no remaining dependency on the old third parameter, and the tests’ semantics around hash/merge behavior and DB state are unchanged.

If you touch this area again, you could optionally DRY up the repeated ctx := context.Background() + LoadConfig(ctx, tempDir) + Close pattern into a small helper (e.g., mustLoadConfig(t, dir) returning the config and registering t.Cleanup), but that’s purely a readability/maintenance nicety, not required for this change.

transports/bifrost-http/handlers/devpprof.go (1)

272-313: Defensively handle missing alloc_objects / alloc_space sample types in getTopAllocations

Right now allocObjectsIdx / allocSpaceIdx default to zero and are used unconditionally. If the parsed profile ever lacks those sample types, you’ll silently read whatever is at index 0, which makes the “top allocations” inaccurate.

Consider tracking booleans when you discover each index and returning an empty slice (or skipping aggregation) if either type is not found, instead of assuming index 0 is correct.

framework/tracing/store.go (1)

13-22: Clarify or drop unused Tracer / RequestID on DeferredSpanInfo

Within this file, only SpanID, StartTime, FirstChunkTime, AccumulatedChunks, and mu are used. If Tracer and RequestID are no longer part of the streaming/deferred‑span design, consider removing them (or explicitly marking them as reserved) to keep the struct lean and avoid confusion about lifecycles.

plugins/logging/main.go (1)

614-625: Make Cleanup idempotent to avoid double‑close panics on done

Cleanup stops the ticker and unconditionally close(p.done) then waits. If Cleanup is ever called twice (tests, repeated shutdown, or RemovePlugin + Shutdown wiring), the second call will panic on closing an already‑closed channel.

Consider guarding with a sync.Once or an atomic flag so the close + wait sequence runs only once.

📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5186ad3 and b12158e.

⛔ Files ignored due to path filters (3)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • plugins/governance/go.sum is excluded by !**/*.sum
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (100)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • .github/workflows/scripts/release-framework.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/governance/version
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/jsonparser/version
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/logging/version
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/maxim/version
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/mocker/version
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/otel/version
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/semanticcache/version
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • plugins/telemetry/version
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/go.mod
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (2)
  • plugins/mocker/version
  • plugins/governance/version
🚧 Files skipped from review as they are similar to previous changes (30)
  • core/version
  • .github/workflows/release-pipeline.yml
  • ui/components/sidebar.tsx
  • .github/workflows/scripts/release-bifrost-http.sh
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • framework/version
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/cache.go
  • core/schemas/context.go
  • plugins/maxim/main.go
  • plugins/governance/changelog.md
  • ui/components/devProfiler.tsx
  • transports/bifrost-http/handlers/config.go
  • .github/workflows/scripts/release-framework.sh
  • docs/docs.json
  • ui/components/ui/sheet.tsx
  • transports/go.mod
  • docs/plugins/writing-plugin.mdx
  • plugins/maxim/version
  • transports/version
  • transports/changelog.md
  • ui/lib/store/apis/devApi.ts
  • plugins/logging/utils.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • ui/components/ui/switch.tsx
  • plugins/jsonparser/version
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • plugins/semanticcache/version
  • ui/lib/store/apis/index.ts
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/mcp.go
  • examples/plugins/hello-world/main.go
  • examples/plugins/hello-world/go.mod
  • framework/streaming/transcription.go
  • plugins/maxim/changelog.md
  • plugins/mocker/main.go
  • plugins/semanticcache/changelog.md
  • transports/bifrost-http/lib/middleware.go
  • docs/plugins/getting-started.mdx
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/providers.go
  • framework/streaming/types.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/inference.go
  • framework/streaming/chat.go
  • core/bifrost.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/mcpserver.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/websocket.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • framework/changelog.md
  • plugins/governance/main.go
  • plugins/semanticcache/main.go
  • core/changelog.md
  • plugins/logging/main.go
  • framework/plugins/dynamicplugin_test.go
  • plugins/otel/version
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/devpprof.go
  • plugins/jsonparser/changelog.md
  • plugins/logging/version
  • framework/streaming/accumulator.go
  • plugins/otel/changelog.md
  • transports/bifrost-http/handlers/session.go
  • plugins/telemetry/version
  • core/schemas/tracer.go
  • plugins/mocker/changelog.md
  • docs/plugins/migration-guide.mdx
  • core/providers/utils/utils.go
  • plugins/jsonparser/main.go
  • core/schemas/bifrost.go
  • framework/tracing/store.go
  • framework/streaming/responses.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/lib/config_test.go
  • plugins/governance/go.mod
  • plugins/otel/converter.go
  • plugins/telemetry/changelog.md
  • framework/tracing/helpers.go
  • plugins/logging/changelog.md
  • ui/app/clientLayout.tsx
  • plugins/otel/main.go
  • transports/bifrost-http/integrations/router.go
  • core/schemas/trace.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/server/server.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/lib/config.go
  • framework/tracing/propagation.go
  • core/bifrost_test.go
🧠 Learnings (6)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/mcp.go
  • examples/plugins/hello-world/main.go
  • framework/streaming/transcription.go
  • plugins/mocker/main.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/providers.go
  • framework/streaming/types.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/inference.go
  • framework/streaming/chat.go
  • core/bifrost.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/mcpserver.go
  • core/schemas/plugin.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/websocket.go
  • plugins/governance/main.go
  • plugins/semanticcache/main.go
  • plugins/logging/main.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/audio.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/devpprof.go
  • framework/streaming/accumulator.go
  • transports/bifrost-http/handlers/session.go
  • core/schemas/tracer.go
  • core/providers/utils/utils.go
  • plugins/jsonparser/main.go
  • core/schemas/bifrost.go
  • framework/tracing/store.go
  • framework/streaming/responses.go
  • framework/plugins/dynamicplugin.go
  • transports/bifrost-http/lib/config_test.go
  • plugins/otel/converter.go
  • framework/tracing/helpers.go
  • plugins/otel/main.go
  • transports/bifrost-http/integrations/router.go
  • core/schemas/trace.go
  • framework/tracing/llmspan.go
  • transports/bifrost-http/server/server.go
  • framework/tracing/tracer.go
  • transports/bifrost-http/lib/config.go
  • framework/tracing/propagation.go
  • core/bifrost_test.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/lib/config.go
📚 Learning: 2025-12-24T04:36:57.698Z
Learnt from: danpiths
Repo: maximhq/bifrost PR: 1169
File: transports/bifrost-http/handlers/governance.go:1708-1878
Timestamp: 2025-12-24T04:36:57.698Z
Learning: In governance update handlers (e.g., updateModelConfig, updateProviderGovernance), design updates to support clearing individual fields by sending null/empty values (e.g., {"rate_limit": {"token_max_limit": null}} clears token_max_limit). Follow this pattern for partial updates so users can remove specific governance settings without deleting the whole entity. Ensure budget updates follow the same approach using direct field assignment. Review input validation, JSON decoding (e.g., pointers vs values in Go structs), and API documentation to reflect nullable fields and expected behavior.

Applied to files:

  • transports/bifrost-http/handlers/governance.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go

@akshaydeo akshaydeo force-pushed the 11-19-plugins_v2_architecture branch from b12158e to 828d3d7 Compare December 26, 2025 03:38
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (3)
plugins/governance/main.go (1)

42-49: Remove TransportInterceptor from BaseGovernancePlugin interface or implement it in GovernancePlugin.

The BaseGovernancePlugin interface declares TransportInterceptor (line 44), but GovernancePlugin implements HTTPTransportMiddleware (line 264) instead. The interface is actively used in transports/bifrost-http/server/server.go for type assertions and method calls. The TransportInterceptor method in the interface is never called and appears to be a leftover from the migration to the new v2 plugin architecture.

transports/bifrost-http/server/server.go (1)

203-212: Avoid returning the shared providers map after releasing the lock (data race risk).

GovernanceInMemoryStore.GetConfiguredProviders returns s.Config.Providers directly while only holding Mu during the read of the field. Callers then iterate/use the same map after the lock is released, while other goroutines (e.g., AddProvider/UpdateProviderConfig/RemoveProvider) may mutate it under Mu. That’s a classic concurrent map read/write hazard.

Make this method return a copy of the map (or a safely immutable view) constructed under the read lock, so callers never share the live internal map.

Proposed fix: copy the providers map under the lock
 type GovernanceInMemoryStore struct {
 	Config *lib.Config
 }

 func (s *GovernanceInMemoryStore) GetConfiguredProviders() map[schemas.ModelProvider]configstore.ProviderConfig {
-	// Use read lock for thread-safe access - no need to copy on hot path
-	s.Config.Mu.RLock()
-	defer s.Config.Mu.RUnlock()
-	return s.Config.Providers
+	// Copy under read lock to avoid exposing the shared map to concurrent writers
+	s.Config.Mu.RLock()
+	defer s.Config.Mu.RUnlock()
+
+	out := make(map[schemas.ModelProvider]configstore.ProviderConfig, len(s.Config.Providers))
+	for k, v := range s.Config.Providers {
+		out[k] = v
+	}
+	return out
 }
transports/bifrost-http/lib/config.go (1)

1509-1527: Guard against nil PricingSyncInterval when initializing framework config from file.

In initFrameworkConfigFromFile, the else-branch:

} else if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
    pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
    syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
    pricingConfig.PricingSyncInterval = &syncDuration
}

assumes PricingSyncInterval is non-nil. If a user provides framework.pricing without pricing_sync_interval, this will panic during config load.

Add a nil (and optional >0) guard, and either fall back to defaults or leave the field nil so modelcatalog.Init can apply its defaults.

Suggested defensive change
 func initFrameworkConfigFromFile(ctx context.Context, config *Config, configData *ConfigData) {
 	pricingConfig := &modelcatalog.Config{}
@@
-	} else if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
-		pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
-		syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
-		pricingConfig.PricingSyncInterval = &syncDuration
+	} else if configData.FrameworkConfig != nil && configData.FrameworkConfig.Pricing != nil {
+		pricingConfig.PricingURL = configData.FrameworkConfig.Pricing.PricingURL
+		if configData.FrameworkConfig.Pricing.PricingSyncInterval != nil &&
+			*configData.FrameworkConfig.Pricing.PricingSyncInterval > 0 {
+			syncDuration := time.Duration(*configData.FrameworkConfig.Pricing.PricingSyncInterval) * time.Second
+			pricingConfig.PricingSyncInterval = &syncDuration
+		}
 	}

If you want to enforce a default when nil, you can set pricingConfig.PricingSyncInterval to bifrost.Ptr(modelcatalog.DefaultPricingSyncInterval) in the else case.

♻️ Duplicate comments (15)
ui/components/ui/sheet.tsx (1)

114-114: Make left margin conditional on button presence.

The ml-3 class is applied unconditionally, but the expand/collapse button only renders when expandable is true AND side is "right". When the button is absent, this creates unwanted left margin.

🔎 Suggested fix
-			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center ml-3", className)}>{children}</div>
+			<div className={cn("flex h-full min-w-0 flex-1 flex-row items-center", sheetContext?.expandable && sheetContext?.side === "right" ? "ml-3" : "", className)}>{children}</div>
transports/changelog.md (1)

15-15: Add language specifier to fenced code block.

The fenced code block is missing a language specifier, which prevents proper syntax highlighting.

🔎 Suggested fix
   **Migration summary:**
-  ```
+  ```go
   // v1.3.x (removed)
   TransportInterceptor(ctx *BifrostContext, url string, headers map[string]string, body map[string]any) (map[string]string, map[string]any, error)

Based on static analysis hints.

core/providers/utils/utils.go (1)

1428-1431: Context should be passed by value, not by pointer.

The function signature completeDeferredSpan(ctx *context.Context, ...) violates Go conventions. The context.Context type is designed to be passed by value and is safe for concurrent use. Passing it by pointer can lead to subtle bugs and is considered an anti-pattern.

🔎 Proposed fix
-func completeDeferredSpan(ctx *context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
-	if ctx == nil {
+func completeDeferredSpan(ctx context.Context, result *schemas.BifrostResponse, err *schemas.BifrostError) {
+	if ctx == nil {
 		return
 	}
 
-	traceID, ok := (*ctx).Value(schemas.BifrostContextKeyTraceID).(string)
+	traceID, ok := ctx.Value(schemas.BifrostContextKeyTraceID).(string)

Update all call sites accordingly:

-completeDeferredSpan(&ctx, processedResponse, processedError)
+completeDeferredSpan(ctx, processedResponse, processedError)
plugins/otel/changelog.md (1)

1-19: LGTM: Changelog comprehensively documents the breaking change.

The changelog clearly documents the migration from TransportInterceptor to HTTPTransportMiddleware with old/new API signatures and consumer guidance. The past review flagged the missing migration guide link, which should be addressed separately.

framework/changelog.md (1)

1-1: Grammar and detail issues in tracing feature entry remain unaddressed.

The changelog entry still has awkward phrasing ("for allowing plugins to enable") and lacks detail about what the tracing framework provides to plugins. The breaking change section below is much more detailed, creating an imbalance.

Consider revising to:

-- feat: adds new tracing framework for allowing plugins to enable e2e tracing
+- feat: adds tracing framework to enable end-to-end distributed tracing. Plugins can now report spans and traces for observability integration via the ObservabilityPlugin interface.

This improves grammar and briefly describes the capability without being overly verbose.

examples/plugins/hello-world/main.go (1)

19-27: Context value propagation still broken between middleware and hooks.

The HTTPTransportMiddleware sets a value on the fasthttp.RequestCtx (line 23), but PreHook (line 30) and PostHook (lines 39-40) attempt to read from *schemas.BifrostContext. These are different context types, so the value will always be nil in the hooks, breaking the example's demonstration of data flow.

As noted in the previous review, you need to either:

  1. Extract the *schemas.BifrostContext from the fasthttp context and set values on it, or
  2. Remove the context value reads from PreHook/PostHook if cross-hook data flow is not needed for this example
ui/app/workspace/logs/views/logDetailsSheet.tsx (1)

183-187: Accessibility: Icon-only dropdown trigger still missing aria-label.

The icon-only Button inside the DropdownMenuTrigger lacks an accessible name for screen reader users. This issue was flagged in a previous review and remains unaddressed.

🔎 Add aria-label for accessibility
 <DropdownMenuTrigger asChild>
-  <Button variant="ghost" size="icon">
+  <Button variant="ghost" size="icon" aria-label="Open log actions menu">
     <MoreVertical className="h-3 w-3" />
   </Button>
 </DropdownMenuTrigger>
transports/bifrost-http/integrations/router.go (1)

517-519: Non-streaming calls pass wrong context type — breaks tracing and plugin propagation.

The non-streaming request handlers call Bifrost client methods with ctx (a *fasthttp.RequestCtx) instead of the enriched *bifrostCtx. The comment on line 508-512 says "Use the cancellable context from ConvertToBifrostContext", yet all the calls below pass ctx:

listModelsResponse, bifrostErr := g.client.ListModelsRequest(ctx, ...)
textCompletionResponse, bifrostErr := g.client.TextCompletionRequest(ctx, ...)
chatResponse, bifrostErr := g.client.ChatCompletionRequest(ctx, ...)
// ... etc

This breaks tracing context propagation and plugin hooks since all BifrostContextKey* values live on bifrostCtx. The streaming path correctly uses streamCtx := *bifrostCtx.

This issue was flagged in a previous review. Please verify that these calls are updated to use *bifrostCtx instead of ctx.

plugins/governance/main.go (1)

292-297: Fix error message accuracy.

Line 294: The error message says "failed to marshal" but the operation is unmarshaling (sonic.Unmarshal).

🔎 Proposed fix
 		err = sonic.Unmarshal(ctx.Request.Body(), &payload)
 		if err != nil {
-			p.logger.Error("failed to marshal request body to check for virtual key: %v", err)
+			p.logger.Error("failed to unmarshal request body: %v", err)
 			next(ctx)
 			return
 		}
framework/tracing/store.go (1)

13-22: Trim unused fields on DeferredSpanInfo and consider cleaning deferredSpans in TTL sweep.

  • DeferredSpanInfo.Tracer and DeferredSpanInfo.RequestID (lines 17–18) are never set or read anywhere; they add complexity and memory overhead without value. Either wire them up or remove them.
  • cleanupOldTraces only evicts from traces (lines 341–355) but leaves any corresponding deferredSpans entries alive; for long‑running processes this can accumulate stale streaming state for traces that have already been TTL‑purged.

You can:

  • Drop the unused fields from DeferredSpanInfo, and
  • In cleanupOldTraces, also s.deferredSpans.Delete(trace.TraceID) when a trace is evicted.
Example cleanup tweaks
 type DeferredSpanInfo struct {
 	SpanID             string
 	StartTime          time.Time
-	Tracer             schemas.Tracer // Reference to tracer for completing the span
-	RequestID          string         // Request ID for accumulator lookup
 	FirstChunkTime     time.Time      // Timestamp of first chunk (for TTFT calculation)
 	AccumulatedChunks  []*schemas.BifrostResponse // Accumulated streaming chunks
 	mu                 sync.Mutex     // Mutex for thread-safe chunk accumulation
 }
@@
 func (s *TraceStore) cleanupOldTraces() {
 	cutoff := time.Now().Add(-s.ttl)
@@
 	s.traces.Range(func(key, value any) bool {
 		trace := value.(*schemas.Trace)
 		if trace.StartTime.Before(cutoff) {
 			if deleted, ok := s.traces.LoadAndDelete(key); ok {
+				// Best-effort cleanup of any deferred span for this trace
+				if t := deleted.(*schemas.Trace); t != nil {
+					s.deferredSpans.Delete(t.TraceID)
+				}
 				s.ReleaseTrace(deleted.(*schemas.Trace))
 				count++
 			}
 		}
 		return true
 	})

Also applies to: 119-177, 341-360

transports/bifrost-http/handlers/middlewares.go (1)

191-279: TracingMiddleware.StartSpan still passes *fasthttp.RequestCtx where context.Context is required.

Middleware() calls:

spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)

Here ctx is *fasthttp.RequestCtx, but StartSpan is defined as:

StartSpan(ctx context.Context, name string, kind schemas.SpanKind) (context.Context, schemas.SpanHandle)

*fasthttp.RequestCtx does not implement context.Context, so this will not compile. You need to construct a real context.Context (and attach the trace ID) before calling StartSpan, then propagate the returned spanCtx as needed.

Suggested fix using a Go context carrying the trace ID
 func (m *TracingMiddleware) Middleware() schemas.BifrostHTTPMiddleware {
 	return func(next fasthttp.RequestHandler) fasthttp.RequestHandler {
 		return func(ctx *fasthttp.RequestCtx) {
@@
-			// Create trace in store - only ID returned (trace data stays in store)
-			traceID := m.tracer.Load().CreateTrace(parentID)
-			// Only trace ID goes into context (lightweight, no bloat)
-			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
+			// Create trace in store - only ID returned (trace data stays in store)
+			traceID := m.tracer.Load().CreateTrace(parentID)
+			// Expose trace ID to downstream handlers via fasthttp user values
+			ctx.SetUserValue(schemas.BifrostContextKeyTraceID, traceID)
@@
-			// Create root span for the HTTP request
-			spanCtx, rootSpan := m.tracer.Load().StartSpan(ctx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
+			// Build a standard context for tracer usage, seeded with the trace ID
+			baseCtx := context.WithValue(context.Background(), schemas.BifrostContextKeyTraceID, traceID)
+			// Create root span for the HTTP request
+			spanCtx, rootSpan := m.tracer.Load().StartSpan(baseCtx, string(ctx.RequestURI()), schemas.SpanKindHTTPRequest)
@@
-				// Set root span ID in context for child span creation
+				// Set root span ID in fasthttp context for child span creation downstream
 				if spanID, ok := spanCtx.Value(schemas.BifrostContextKeySpanID).(string); ok {
 					ctx.SetUserValue(schemas.BifrostContextKeySpanID, spanID)
 				}

This keeps tracing on a proper context.Context while still exposing IDs via ctx.UserValue for HTTP handlers and Bifrost to consume.

core/schemas/trace.go (1)

40-49: Guard Trace.Reset, Span.End, and Span.Reset with mutexes or document exclusive-use assumptions.

Trace.Reset (lines 40–49) and Span.End / Span.Reset (lines 84–104) mutate shared state without taking their respective mutexes, while other mutators (AddSpan, SetAttribute, AddEvent) are locked. If spans or traces are read/updated concurrently (e.g., while exporters or middleware run), this can lead to data races.

Either:

  • Protect these methods with the existing mutexes, or
  • Clearly document that they may only be called once no other goroutine can access the trace/span and ensure all call sites respect that contract.
Illustrative locking changes
 func (t *Trace) Reset() {
-	t.TraceID = ""
-	t.ParentID = ""
-	t.RootSpan = nil
-	t.Spans = t.Spans[:0]
-	t.StartTime = time.Time{}
-	t.EndTime = time.Time{}
-	t.Attributes = nil
+	t.mu.Lock()
+	defer t.mu.Unlock()
+	t.TraceID = ""
+	t.ParentID = ""
+	t.RootSpan = nil
+	t.Spans = t.Spans[:0]
+	t.StartTime = time.Time{}
+	t.EndTime = time.Time{}
+	t.Attributes = nil
 }
@@
 func (s *Span) End(status SpanStatus, statusMsg string) {
-	s.EndTime = time.Now()
-	s.Status = status
-	s.StatusMsg = statusMsg
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.EndTime = time.Now()
+	s.Status = status
+	s.StatusMsg = statusMsg
 }
@@
 func (s *Span) Reset() {
-	s.SpanID = ""
-	s.ParentID = ""
-	s.TraceID = ""
-	s.Name = ""
-	s.Kind = SpanKindUnspecified
-	s.StartTime = time.Time{}
-	s.EndTime = time.Time{}
-	s.Status = SpanStatusUnset
-	s.StatusMsg = ""
-	s.Attributes = nil
-	s.Events = s.Events[:0]
+	s.mu.Lock()
+	defer s.mu.Unlock()
+	s.SpanID = ""
+	s.ParentID = ""
+	s.TraceID = ""
+	s.Name = ""
+	s.Kind = SpanKindUnspecified
+	s.StartTime = time.Time{}
+	s.EndTime = time.Time{}
+	s.Status = SpanStatusUnset
+	s.StatusMsg = ""
+	s.Attributes = nil
+	s.Events = s.Events[:0]
 }

Also applies to: 84-104

plugins/otel/converter.go (1)

148-228: Guard against uint64int64 overflow in anyToKeyValue.

The uint64 case in anyToKeyValue casts directly to int64:

case uint64:
    return kvInt(key, int64(v))

For values > math.MaxInt64 this overflows and will produce negative numbers. That’s rare but possible for counters/IDs.

Example overflow-safe handling
 import (
 	"encoding/hex"
 	"fmt"
 	"strings"
+	"math"
@@
 	case uint64:
-		return kvInt(key, int64(v))
+		if v > math.MaxInt64 {
+			// Clamp and preserve sign; optionally log via plugin logger if available
+			return kvInt(key, math.MaxInt64)
+		}
+		return kvInt(key, int64(v))

If you’d rather not clamp silently, you could instead convert large uint64 values to strings (kvStr) to preserve the full value.

In Go, does converting a uint64 value larger than math.MaxInt64 to int64 overflow, and is clamping or string-encoding preferred when exporting such values to OpenTelemetry integer attributes?
framework/tracing/tracer.go (1)

233-455: Ensure deterministic ordering of choices and tool calls in buildCompleteResponseFromChunks.

You build choiceIndices and tcIndices from maps but never sort them, so choice order and tool-call order will vary between runs despite the “Sort … for deterministic output” comments.

Sorting these index slices before iterating will make reconstructed responses stable (useful for tests and for downstream observability tooling that expects consistent ordering).

Proposed ordering fix
@@
 	// Build final choices from accumulated data
 	// Sort choice indices for deterministic output
 	choiceIndices := make([]int, 0, len(choiceMap))
 	for idx := range choiceMap {
 		choiceIndices = append(choiceIndices, idx)
 	}
+	sort.Ints(choiceIndices)
@@
 			if len(accum.toolCalls) > 0 {
 				// Sort tool calls by index
 				tcIndices := make([]int, 0, len(accum.toolCalls))
 				for tcIdx := range accum.toolCalls {
 					tcIndices = append(tcIndices, tcIdx)
 				}
+				sort.Ints(tcIndices)
 				toolCalls := make([]schemas.ChatAssistantMessageToolCall, 0, len(accum.toolCalls))
 				for _, tcIdx := range tcIndices {
 					toolCalls = append(toolCalls, accum.toolCalls[tcIdx])
 				}

Remember to add:

import "sort"

at the top of the file.

framework/tracing/llmspan.go (1)

190-193: ExtraParams still written without namespacing (duplicate concern).

User-provided keys from ExtraParams continue to be written directly into the attributes map across all request types without a namespace prefix. This can cause collisions with standard attribute keys (e.g., if a user sets "gen_ai.request.model" in ExtraParams, it would overwrite line 22's value).

This pattern appears 14 times throughout the file in all request attribute population functions.

🔎 Suggested approach: namespace ExtraParams consistently

Apply a prefix to all ExtraParams keys before insertion. Example for one location:

 	// ExtraParams
 	for k, v := range req.Params.ExtraParams {
-		attrs[k] = fmt.Sprintf("%v", v)
+		attrs["gen_ai.extra."+k] = fmt.Sprintf("%v", v)
 	}

Repeat this pattern at all 14 occurrences to prevent attribute key collisions.

Also applies to: 298-300, 365-367, 575-577, 708-710, 738-740, 753-755, 768-770, 783-785, 958-960, 982-984, 997-999, 1012-1014, 1027-1029

🧹 Nitpick comments (3)
ui/lib/store/apis/devApi.ts (1)

59-59: Misleading comment about polling.

The comment "polls every 10 seconds" suggests automatic polling, but RTK Query's builder.query doesn't configure polling by default. Polling is typically configured at the hook usage site via the pollingInterval option. Consider updating the comment to reflect this, or remove it to avoid confusion.

🔎 Proposed fix
   endpoints: (builder) => ({
-    // Get dev pprof data - polls every 10 seconds
+    // Get dev pprof data
     getDevPprof: builder.query<PprofData, void>({
transports/bifrost-http/handlers/devpprof.go (1)

104-122: Global collector singleton is safe but consider handler lifecycle.

The sync.Once pattern ensures thread-safe singleton initialization. However, since globalCollector is package-level, multiple DevPprofHandler instances will share the same collector. This is likely intentional but worth noting.

Consider documenting this behavior or ensuring Cleanup() is only called once during server shutdown.

framework/tracing/llmspan.go (1)

1279-1293: Consider strings.Builder for string concatenation in loops (optional optimization).

Lines 1279-1283 and 1289-1293 use += for string concatenation in loops, which can be less efficient for multiple blocks. Using strings.Builder (as done in extractMessageContent at lines 1315-1321) would be more efficient.

However, given typical message sizes in practice, the current approach is acceptable.

🔎 Optional refactor using strings.Builder
 		content := ""
+		var contentBuilder strings.Builder
 		if msg.Content != nil {
 			if msg.Content.ContentStr != nil && *msg.Content.ContentStr != "" {
 				content = *msg.Content.ContentStr
 			} else if msg.Content.ContentBlocks != nil {
 				for _, block := range msg.Content.ContentBlocks {
 					if block.Text != nil {
-						content += *block.Text
+						contentBuilder.WriteString(*block.Text)
 					}
 				}
+				content = contentBuilder.String()
 			}
 		}
 		// Extract reasoning text
 		reasoning := ""
+		var reasoningBuilder strings.Builder
 		if msg.ResponsesReasoning != nil && msg.ResponsesReasoning.Summary != nil {
 			for _, block := range msg.ResponsesReasoning.Summary {
 				if block.Text != "" {
-					reasoning += block.Text
+					reasoningBuilder.WriteString(block.Text)
 				}
 			}
+			reasoning = reasoningBuilder.String()
 		}
📜 Review details

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b12158e and 828d3d7.

⛔ Files ignored due to path filters (2)
  • examples/plugins/hello-world/go.sum is excluded by !**/*.sum
  • transports/go.sum is excluded by !**/*.sum
📒 Files selected for processing (92)
  • .github/workflows/release-pipeline.yml
  • .github/workflows/scripts/push-mintlify-changelog.sh
  • .github/workflows/scripts/release-bifrost-http.sh
  • .github/workflows/scripts/release-framework.sh
  • core/bifrost.go
  • core/bifrost_test.go
  • core/changelog.md
  • core/mcp.go
  • core/providers/utils/utils.go
  • core/schemas/bifrost.go
  • core/schemas/context.go
  • core/schemas/plugin.go
  • core/schemas/trace.go
  • core/schemas/tracer.go
  • core/version
  • docs/docs.json
  • docs/plugins/getting-started.mdx
  • docs/plugins/migration-guide.mdx
  • docs/plugins/writing-plugin.mdx
  • examples/plugins/hello-world/go.mod
  • examples/plugins/hello-world/main.go
  • framework/changelog.md
  • framework/configstore/tables/mcp.go
  • framework/plugins/dynamicplugin.go
  • framework/plugins/dynamicplugin_test.go
  • framework/streaming/accumulator.go
  • framework/streaming/audio.go
  • framework/streaming/chat.go
  • framework/streaming/responses.go
  • framework/streaming/transcription.go
  • framework/streaming/types.go
  • framework/tracing/helpers.go
  • framework/tracing/llmspan.go
  • framework/tracing/propagation.go
  • framework/tracing/store.go
  • framework/tracing/tracer.go
  • framework/version
  • plugins/governance/changelog.md
  • plugins/governance/go.mod
  • plugins/governance/main.go
  • plugins/jsonparser/changelog.md
  • plugins/jsonparser/main.go
  • plugins/logging/changelog.md
  • plugins/logging/main.go
  • plugins/logging/utils.go
  • plugins/maxim/changelog.md
  • plugins/maxim/main.go
  • plugins/mocker/changelog.md
  • plugins/mocker/main.go
  • plugins/otel/changelog.md
  • plugins/otel/converter.go
  • plugins/otel/main.go
  • plugins/otel/ttlsyncmap.go
  • plugins/semanticcache/changelog.md
  • plugins/semanticcache/main.go
  • plugins/telemetry/changelog.md
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/devpprof_unix.go
  • transports/bifrost-http/handlers/devpprof_windows.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/handlers/logging.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/plugins.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/changelog.md
  • transports/go.mod
  • transports/version
  • ui/app/clientLayout.tsx
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • ui/components/devProfiler.tsx
  • ui/components/sidebar.tsx
  • ui/components/ui/sheet.tsx
  • ui/components/ui/switch.tsx
  • ui/lib/store/apis/devApi.ts
  • ui/lib/store/apis/index.ts
💤 Files with no reviewable changes (3)
  • core/mcp.go
  • framework/configstore/tables/mcp.go
  • plugins/otel/ttlsyncmap.go
✅ Files skipped from review due to trivial changes (1)
  • docs/plugins/migration-guide.mdx
🚧 Files skipped from review as they are similar to previous changes (26)
  • core/version
  • ui/components/sidebar.tsx
  • .github/workflows/scripts/release-framework.sh
  • framework/streaming/responses.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/logging.go
  • transports/version
  • transports/bifrost-http/handlers/providers.go
  • transports/go.mod
  • transports/bifrost-http/handlers/devpprof_windows.go
  • core/bifrost_test.go
  • docs/plugins/writing-plugin.mdx
  • core/schemas/bifrost.go
  • .github/workflows/scripts/release-bifrost-http.sh
  • framework/streaming/chat.go
  • plugins/maxim/main.go
  • docs/docs.json
  • plugins/telemetry/main.go
  • framework/version
  • transports/bifrost-http/handlers/plugins.go
  • framework/plugins/dynamicplugin.go
  • framework/tracing/helpers.go
  • transports/bifrost-http/handlers/websocket.go
  • transports/bifrost-http/handlers/health.go
  • transports/bifrost-http/handlers/mcpserver.go
  • transports/bifrost-http/handlers/devpprof_unix.go
🧰 Additional context used
📓 Path-based instructions (1)
**

⚙️ CodeRabbit configuration file

always check the stack if there is one for the current PR. do not give localized reviews for the PR, always see all changes in the light of the whole stack of PRs (if there is a stack, if there is no stack you can continue to make localized suggestions/reviews)

Files:

  • plugins/mocker/changelog.md
  • core/changelog.md
  • ui/components/devProfiler.tsx
  • transports/bifrost-http/handlers/config.go
  • framework/streaming/transcription.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • plugins/logging/utils.go
  • ui/app/workspace/logs/views/logDetailsSheet.tsx
  • framework/tracing/propagation.go
  • plugins/logging/changelog.md
  • plugins/governance/main.go
  • core/providers/utils/utils.go
  • transports/changelog.md
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/ui.go
  • plugins/otel/changelog.md
  • transports/bifrost-http/handlers/cache.go
  • plugins/logging/main.go
  • docs/plugins/getting-started.mdx
  • transports/bifrost-http/handlers/session.go
  • framework/streaming/audio.go
  • framework/changelog.md
  • ui/components/ui/sheet.tsx
  • transports/bifrost-http/integrations/router.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/inference.go
  • plugins/semanticcache/changelog.md
  • transports/bifrost-http/lib/middleware.go
  • examples/plugins/hello-world/go.mod
  • framework/plugins/dynamicplugin_test.go
  • core/schemas/tracer.go
  • transports/bifrost-http/server/server.go
  • plugins/maxim/changelog.md
  • ui/components/ui/switch.tsx
  • plugins/jsonparser/main.go
  • plugins/jsonparser/changelog.md
  • plugins/otel/main.go
  • examples/plugins/hello-world/main.go
  • core/bifrost.go
  • plugins/governance/go.mod
  • transports/bifrost-http/handlers/mcp.go
  • ui/app/clientLayout.tsx
  • framework/tracing/store.go
  • ui/lib/store/apis/devApi.ts
  • core/schemas/trace.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/streaming/types.go
  • plugins/telemetry/changelog.md
  • transports/bifrost-http/handlers/integrations.go
  • framework/tracing/tracer.go
  • core/schemas/plugin.go
  • plugins/governance/changelog.md
  • plugins/otel/converter.go
  • transports/bifrost-http/lib/config_test.go
  • framework/tracing/llmspan.go
  • framework/streaming/accumulator.go
  • core/schemas/context.go
  • plugins/semanticcache/main.go
  • ui/lib/store/apis/index.ts
🧠 Learnings (5)
📚 Learning: 2025-12-09T17:07:42.007Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/schemas/account.go:9-18
Timestamp: 2025-12-09T17:07:42.007Z
Learning: In core/schemas/account.go, the HuggingFaceKeyConfig field within the Key struct is currently unused and reserved for future Hugging Face inference endpoint deployments. Do not flag this field as missing from OpenAPI documentation or require its presence in the API spec until the feature is actively implemented and used. When the feature is added, update the OpenAPI docs accordingly; otherwise, treat this field as non-breaking and not part of the current API surface.

Applied to files:

  • transports/bifrost-http/handlers/config.go
  • framework/streaming/transcription.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • plugins/logging/utils.go
  • framework/tracing/propagation.go
  • plugins/governance/main.go
  • core/providers/utils/utils.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/cache.go
  • plugins/logging/main.go
  • transports/bifrost-http/handlers/session.go
  • framework/streaming/audio.go
  • transports/bifrost-http/integrations/router.go
  • plugins/mocker/main.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/middleware.go
  • framework/plugins/dynamicplugin_test.go
  • core/schemas/tracer.go
  • transports/bifrost-http/server/server.go
  • plugins/jsonparser/main.go
  • plugins/otel/main.go
  • examples/plugins/hello-world/main.go
  • core/bifrost.go
  • transports/bifrost-http/handlers/mcp.go
  • framework/tracing/store.go
  • core/schemas/trace.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • framework/streaming/types.go
  • transports/bifrost-http/handlers/integrations.go
  • framework/tracing/tracer.go
  • core/schemas/plugin.go
  • plugins/otel/converter.go
  • transports/bifrost-http/lib/config_test.go
  • framework/tracing/llmspan.go
  • framework/streaming/accumulator.go
  • core/schemas/context.go
  • plugins/semanticcache/main.go
📚 Learning: 2025-12-12T08:25:02.629Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1000
File: transports/bifrost-http/integrations/router.go:709-712
Timestamp: 2025-12-12T08:25:02.629Z
Learning: In transports/bifrost-http/**/*.go, update streaming response handling to align with OpenAI Responses API: use typed SSE events such as response.created, response.output_text.delta, response.done, etc., and do not rely on the legacy data: [DONE] termination marker. Note that data: [DONE] is only used by the older Chat Completions and Text Completions streaming APIs. Ensure parsers, writers, and tests distinguish SSE events from the [DONE] sentinel and handle each event type accordingly for correct stream termination and progress updates.

Applied to files:

  • transports/bifrost-http/handlers/config.go
  • transports/bifrost-http/handlers/middlewares_test.go
  • transports/bifrost-http/handlers/devpprof.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/handlers/cache.go
  • transports/bifrost-http/handlers/session.go
  • transports/bifrost-http/integrations/router.go
  • transports/bifrost-http/handlers/inference.go
  • transports/bifrost-http/lib/middleware.go
  • transports/bifrost-http/server/server.go
  • transports/bifrost-http/handlers/mcp.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/handlers/middlewares.go
  • transports/bifrost-http/handlers/integrations.go
  • transports/bifrost-http/lib/config_test.go
📚 Learning: 2025-12-24T10:55:31.424Z
Learnt from: akshaydeo
Repo: maximhq/bifrost PR: 888
File: framework/tracing/propagation.go:24-36
Timestamp: 2025-12-24T10:55:31.424Z
Learning: In framework/tracing/propagation.go, ensure ExtractParentID() returns ctx.TraceID (not ctx.ParentID) because the parent request's traceparent header carries the trace ID used for continuing the trace.

Applied to files:

  • framework/tracing/propagation.go
📚 Learning: 2025-12-22T10:50:40.990Z
Learnt from: Pratham-Mishra04
Repo: maximhq/bifrost PR: 1154
File: plugins/governance/store.go:1165-1186
Timestamp: 2025-12-22T10:50:40.990Z
Learning: In the Bifrost governance plugin, budgets and rate limits have 1:1 relationships with their parent entities (virtual keys, teams, customers). Do not assume sharing; ensure cascade deletion logic only deletes budgets/rate limits when there are no shared references. Enforce invariants in code and add tests to verify no cross-entity sharing and that cascade deletes only remove the specific child of the parent. If a counterexample arises, adjust data model or add guards.

Applied to files:

  • plugins/governance/main.go
📚 Learning: 2025-12-19T09:26:54.961Z
Learnt from: qwerty-dvorak
Repo: maximhq/bifrost PR: 1006
File: core/providers/utils/utils.go:1050-1051
Timestamp: 2025-12-19T09:26:54.961Z
Learning: Update streaming end-marker handling so HuggingFace is treated as a non-[DONE] provider for backends that do not emit a DONE marker (e.g., meta llama on novita). In core/providers/utils/utils.go, adjust ProviderSendsDoneMarker() (or related logic) to detect providers that may not emit DONE and avoid relying on DONE as the sole end signal. Add tests to cover both DONE-emitting and non-DONE backends, with clear documentation in code comments explaining the rationale and any fallback behavior.

Applied to files:

  • core/providers/utils/utils.go
🧬 Code graph analysis (33)
ui/components/devProfiler.tsx (3)
npx/bin.js (3)
  • k (178-178)
  • sizes (179-179)
  • data (172-172)
ui/lib/utils/port.ts (1)
  • isDevelopmentMode (110-112)
ui/components/ui/tooltip.tsx (1)
  • Tooltip (43-43)
transports/bifrost-http/handlers/config.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/transcription.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/handlers/middlewares_test.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/app/workspace/logs/views/logDetailsSheet.tsx (4)
ui/components/ui/sheet.tsx (1)
  • SheetContent (137-137)
ui/components/ui/alertDialog.tsx (9)
  • AlertDialog (83-83)
  • AlertDialogTrigger (93-93)
  • AlertDialogContent (86-86)
  • AlertDialogHeader (89-89)
  • AlertDialogTitle (92-92)
  • AlertDialogDescription (87-87)
  • AlertDialogFooter (88-88)
  • AlertDialogCancel (85-85)
  • AlertDialogAction (84-84)
ui/components/ui/dropdownMenu.tsx (5)
  • DropdownMenu (193-193)
  • DropdownMenuTrigger (207-207)
  • DropdownMenuContent (195-195)
  • DropdownMenuItem (197-197)
  • DropdownMenuSeparator (202-202)
ui/components/ui/button.tsx (1)
  • Button (70-70)
plugins/governance/main.go (3)
core/utils.go (1)
  • Ptr (56-58)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/devpprof.go (4)
ui/lib/types/logs.ts (1)
  • Function (152-157)
ui/lib/store/apis/devApi.ts (6)
  • MemoryStats (4-10)
  • CPUStats (13-17)
  • RuntimeStats (20-26)
  • AllocationInfo (29-35)
  • HistoryPoint (38-45)
  • PprofData (48-55)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
transports/bifrost-http/handlers/utils.go (1)
  • SendJSON (16-22)
transports/bifrost-http/handlers/ui.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/cache.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/session.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/streaming/audio.go (1)
framework/streaming/types.go (1)
  • AccumulatedData (28-48)
transports/bifrost-http/integrations/router.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (2)
  • BifrostContextKeyDeferTraceCompletion (148-148)
  • BifrostContextKeyTraceCompleter (149-149)
plugins/mocker/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/inference.go (2)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (148-148)
transports/bifrost-http/lib/middleware.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/plugins/dynamicplugin_test.go (1)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/tracer.go (7)
core/schemas/bifrost.go (2)
  • ModelProvider (33-33)
  • BifrostError (490-499)
core/schemas/chatcompletions.go (1)
  • BifrostLLMUsage (854-861)
core/schemas/speech.go (1)
  • BifrostSpeechResponse (22-29)
core/schemas/transcriptions.go (1)
  • BifrostTranscriptionResponse (16-26)
core/schemas/trace.go (3)
  • Trace (10-19)
  • SpanKind (115-115)
  • SpanStatus (143-143)
core/schemas/context.go (1)
  • BifrostContext (32-42)
framework/tracing/tracer.go (1)
  • Tracer (17-20)
transports/bifrost-http/server/server.go (8)
transports/bifrost-http/handlers/devpprof.go (3)
  • DevPprofHandler (100-102)
  • IsDevMode (109-111)
  • NewDevPprofHandler (125-129)
core/schemas/plugin.go (3)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
  • ObservabilityPlugin (123-137)
plugins/governance/main.go (2)
  • Config (34-36)
  • PluginName (23-23)
plugins/telemetry/main.go (2)
  • Config (64-66)
  • PluginName (21-21)
transports/bifrost-http/lib/config.go (2)
  • Config (189-220)
  • LoadConfig (276-356)
framework/configstore/clientconfig.go (1)
  • ClientConfig (37-54)
framework/tracing/store.go (1)
  • NewTraceStore (40-67)
framework/tracing/tracer.go (1)
  • NewTracer (24-29)
plugins/jsonparser/main.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
plugins/otel/main.go (2)
core/schemas/plugin.go (1)
  • ObservabilityPlugin (123-137)
core/schemas/trace.go (1)
  • Trace (10-19)
examples/plugins/hello-world/main.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
transports/bifrost-http/handlers/mcp.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
ui/app/clientLayout.tsx (1)
ui/components/devProfiler.tsx (1)
  • DevProfiler (55-422)
framework/tracing/store.go (4)
core/schemas/tracer.go (1)
  • Tracer (38-116)
core/schemas/bifrost.go (1)
  • BifrostResponse (348-369)
core/schemas/trace.go (6)
  • Trace (10-19)
  • Span (52-65)
  • SpanEvent (107-111)
  • SpanKind (115-115)
  • SpanStatusUnset (147-147)
  • SpanStatus (143-143)
framework/tracing/helpers.go (3)
  • GetTrace (23-29)
  • AddSpan (32-38)
  • EndSpan (50-56)
core/schemas/trace.go (2)
framework/tracing/helpers.go (1)
  • AddSpan (32-38)
ui/lib/constants/logs.ts (1)
  • Status (164-164)
transports/bifrost-http/handlers/middlewares.go (5)
core/schemas/plugin.go (2)
  • BifrostHTTPMiddleware (38-38)
  • Plugin (71-97)
transports/bifrost-http/lib/middleware.go (1)
  • ChainMiddlewares (11-23)
core/schemas/tracer.go (1)
  • Tracer (38-116)
framework/tracing/propagation.go (1)
  • ExtractParentID (74-84)
core/schemas/trace.go (3)
  • SpanKindHTTPRequest (131-131)
  • SpanStatusError (151-151)
  • SpanStatusOk (149-149)
transports/bifrost-http/handlers/integrations.go (1)
core/schemas/plugin.go (1)
  • BifrostHTTPMiddleware (38-38)
framework/tracing/tracer.go (7)
framework/tracing/store.go (1)
  • TraceStore (25-37)
framework/streaming/accumulator.go (2)
  • Accumulator (14-30)
  • NewAccumulator (440-480)
core/schemas/tracer.go (3)
  • Tracer (38-116)
  • SpanHandle (11-11)
  • StreamAccumulatorResult (15-33)
framework/tracing/helpers.go (3)
  • GetTraceID (11-20)
  • EndSpan (50-56)
  • GetTrace (23-29)
core/schemas/bifrost.go (5)
  • BifrostContextKeySpanID (145-145)
  • BifrostRequest (175-196)
  • BifrostResponse (348-369)
  • BifrostError (490-499)
  • BifrostContextKeyRequestID (125-125)
framework/tracing/llmspan.go (1)
  • PopulateRequestAttributes (14-60)
framework/streaming/types.go (1)
  • StreamResponseTypeFinal (24-24)
core/schemas/plugin.go (2)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
core/schemas/trace.go (1)
  • Trace (10-19)
transports/bifrost-http/lib/config_test.go (1)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (276-356)
framework/streaming/accumulator.go (1)
framework/streaming/types.go (3)
  • StreamAccumulator (103-115)
  • ChatStreamChunk (77-87)
  • ResponsesStreamChunk (90-100)
core/schemas/context.go (1)
core/schemas/bifrost.go (1)
  • BifrostContextKeyDeferTraceCompletion (148-148)
plugins/semanticcache/main.go (2)
core/schemas/plugin.go (2)
  • Plugin (71-97)
  • BifrostHTTPMiddleware (38-38)
examples/plugins/hello-world/main.go (1)
  • HTTPTransportMiddleware (19-27)
🪛 markdownlint-cli2 (0.18.1)
transports/changelog.md

15-15: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

Comment on lines 19 to +21
golang.org/x/arch v0.23.0 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
golang.org/x/arch v0.22.0 // indirect
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Duplicate golang.org/x/arch dependency entries.

The go.mod file contains two conflicting entries for golang.org/x/arch:

  • Line 19: golang.org/x/arch v0.23.0 // indirect
  • Line 21: golang.org/x/arch v0.22.0 // indirect

This is invalid go.mod syntax and will cause module resolution errors. Run go mod tidy to clean up the dependency graph and remove the duplicate.

🔎 Fix by running go mod tidy
cd examples/plugins/hello-world && go mod tidy

This will resolve the conflict and keep only one version of the dependency.

🤖 Prompt for AI Agents
In examples/plugins/hello-world/go.mod around lines 19 to 21 there are
duplicate/conflicting entries for golang.org/x/arch (v0.23.0 and v0.22.0) which
breaks module resolution; run `cd examples/plugins/hello-world && go mod tidy`
to let Go reconcile and remove the duplicate, or manually delete the
older/incorrect golang.org/x/arch line so only the resolved version remains and
then run `go mod tidy` to update go.sum.

Comment on lines +54 to +59
// spanHandle is the concrete implementation of schemas.SpanHandle for StoreTracer.
// It contains the trace and span IDs needed to reference the span in the store.
type spanHandle struct {
traceID string
spanID string
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Update spanHandle comment to match the actual type name.

The comment still refers to “StoreTracer” but the type is Tracer. This is minor, but it’s confusing when skimming the file; please rename it to reference Tracer.

Suggested doc tweak
-// spanHandle is the concrete implementation of schemas.SpanHandle for StoreTracer.
+// spanHandle is the concrete implementation of schemas.SpanHandle for Tracer.
 // It contains the trace and span IDs needed to reference the span in the store.
 type spanHandle struct {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
// spanHandle is the concrete implementation of schemas.SpanHandle for StoreTracer.
// It contains the trace and span IDs needed to reference the span in the store.
type spanHandle struct {
traceID string
spanID string
}
// spanHandle is the concrete implementation of schemas.SpanHandle for Tracer.
// It contains the trace and span IDs needed to reference the span in the store.
type spanHandle struct {
traceID string
spanID string
}
🤖 Prompt for AI Agents
In framework/tracing/tracer.go around lines 54 to 59, the type comment for
spanHandle incorrectly references "StoreTracer"; update the doc comment to
reference the actual type name "Tracer" (or make it generic) so it matches the
type declaration and clearly describes that spanHandle is the concrete
implementation of schemas.SpanHandle for Tracer and contains the trace and span
IDs used to reference the span.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants