Skip to content

Conversation

akshaydeo
Copy link
Contributor

@akshaydeo akshaydeo commented Sep 15, 2025

Summary

Refactors the HTTP server implementation by extracting it into a dedicated BifrostHTTPServer struct in a new file, improving code organization and maintainability.

Changes

  • Created a new server.go file with a dedicated BifrostHTTPServer struct to encapsulate server functionality
  • Added a default value of 100MB for MaxRequestBodySizeMB in the TableClientConfig struct
  • Added a SetLogger function to the handlers package to allow dependency injection of the logger
  • Simplified the main.go file by delegating server initialization and startup to the new server struct
  • Removed whitespace in SQL trigger definition in the TableVirtualKey struct

Type of change

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

Affected areas

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

How to test

# Core/Transports
go version
go test ./...
go run ./transports/bifrost-http/main.go

Breaking changes

  • No
  • Yes

Related issues

Improves code organization and maintainability

Security considerations

No security implications as this is a refactoring of existing code.

Checklist

  • I read docs/contributing/README.md and followed the guidelines
  • I added/updated tests where appropriate
  • I updated documentation where needed
  • I verified builds succeed (Go and UI)
  • I verified the CI pipeline passes locally if applicable

Note

Adds OTEL tracing plugin with UI, dynamic plugin reload/remove, a refactored HTTP server, context-aware stores, and several core/plugin fixes (latency, governance/logging, Maxim).

  • Observability/Plugins:
    • OTEL Plugin: New plugins/otel with HTTP/gRPC emit, GenAI attrs, TTL map; config schema and example docker-compose.
    • Dynamic Plugins: core supports atomic plugin list, ReloadPlugin/RemovePlugin; HTTP API to manage plugins.
  • HTTP Transport:
    • Server Refactor: New BifrostHTTPServer (server.go), middleware chaining (CORS), SetLogger, route registrations updated.
    • Handlers: All routes accept middlewares; governance/MCP/cache/logging handlers use ctx; new UI/static wiring.
  • Framework/Stores:
    • Widespread addition of context.Context to config/log store APIs, Close() methods; migrations use ctx; default MaxRequestBodySizeMB=100.
    • Pricing manager/init and sync now ctx-aware, improved periodic sync.
  • Core:
    • Plugins held via atomic.Pointer; latency captured as int64; Bedrock latency type aligned; new streaming accumulator structs.
  • Plugins:
    • Governance/Logging now ctx-driven; better error capture; Maxim config fields renamed (APIKey,LogRepoID) and PostHook simplified.
  • UI:
    • New Observability page (OTEL & Maxim forms), copyable inputs, select disabled states; split Virtual Keys and Teams/Customers pages; enterprise fallbacks.
  • Schema/Deps:
    • transports/config.schema.json plugin array + OTEL schema; bumped gorm, deps; misc fixes (Makefile cleanup, not-found page).

Written by Cursor Bugbot for commit cd8a4fb. This will update automatically on new commits. Configure here.

Copy link
Contributor

coderabbitai bot commented Sep 15, 2025

Warning

Rate limit exceeded

@akshaydeo has exceeded the limit for the number of commits or files that can be reviewed per hour. Please wait 11 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

📥 Commits

Reviewing files that changed from the base of the PR and between 025f8a9 and cec64bb.

⛔ Files ignored due to path filters (2)
  • plugins/logging/go.sum is excluded by !**/*.sum
  • plugins/otel/go.sum is excluded by !**/*.sum
📒 Files selected for processing (25)
  • core/schemas/chatcompletions.go (12 hunks)
  • core/schemas/mux.go (10 hunks)
  • core/schemas/providers/anthropic/chat.go (18 hunks)
  • core/schemas/providers/anthropic/responses.go (21 hunks)
  • core/schemas/providers/bedrock/responses.go (6 hunks)
  • core/schemas/providers/cohere/responses.go (8 hunks)
  • core/schemas/providers/openai/text.go (1 hunks)
  • core/schemas/utils.go (0 hunks)
  • framework/configstore/migrations.go (5 hunks)
  • framework/streaming/accumulator.go (1 hunks)
  • framework/streaming/audio.go (1 hunks)
  • framework/streaming/chat.go (1 hunks)
  • framework/streaming/transcription.go (1 hunks)
  • plugins/logging/go.mod (2 hunks)
  • plugins/logging/main.go (16 hunks)
  • plugins/otel/converter.go (1 hunks)
  • plugins/otel/go.mod (1 hunks)
  • plugins/otel/grpc.go (1 hunks)
  • plugins/otel/main.go (1 hunks)
  • tests/core-providers/scenarios/multiple_images.go (1 hunks)
  • tests/core-providers/scenarios/reasoning.go (2 hunks)
  • tests/core-providers/scenarios/response_validation.go (3 hunks)
  • tests/core-providers/scenarios/utils.go (7 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • ui/app/teams-customers/page.tsx (2 hunks)

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

📝 Walkthrough

Summary by CodeRabbit

  • New Features
    • Observability hub with OTLP (HTTP/gRPC) and Maxim connectors, plus Docker Compose stack for tracing/metrics.
    • Dynamic plugin reloads/removal without restarts.
    • PostgreSQL support for configuration and logs stores.
    • Virtual Keys: per-provider weights/model allowlists; adaptive routing middleware.
  • Improvements
    • More reliable streaming (chat/audio/transcription) processing and logging.
    • Context-aware operations for safer shutdowns and better telemetry.
  • UI
    • New Virtual Keys page; revamped Teams & Customers layout.
    • Sidebar reorganized; enterprise fallbacks (SCIM, Cluster, Adaptive routing).
    • Better tables, copyable inputs, disabled select reasons; 404 page.
  • Bug Fixes
    • Provider deletion from UI, accurate token counts, proper streaming error display.

Walkthrough

Introduces context-aware RDB stores (Postgres/SQLite), a new HTTP server with middleware and UI, OTEL plugin, streaming accumulator, plugin hot-reload, and extensive schema refactors (pointer-to-slice changes). Updates pricing sync, governance, logging, and handlers to use context. Adds UI for observability, restructures virtual key governance, and broadens config schema.

Changes

Cohort / File(s) Summary
HTTP Server & Handlers
transports/bifrost-http/main.go, transports/bifrost-http/handlers/server.go, .../handlers/init.go, .../handlers/middlewares.go, .../handlers/ui.go, .../handlers/websocket.go, .../handlers/*.go, .../handlers/handlers.go
Adds a server type with bootstrap/start, middleware system (CORS, VK routing), UI handler, context-aware routes, plugin loader interface, and refactors handlers to accept middlewares and pass ctx. Removes old version var; moves SetVersion to init.go.
Config Store (RDB) & Migrations
framework/configstore/{rdb.go,postgres.go,sqlite.go,store.go,config.go,migrations.go,utils.go,tables.go}
Introduces RDBConfigStore with context-aware CRUD, adds Postgres backend, expands VirtualKey model (provider configs, FKs), creates provider config table, adds validation hooks and triggers, and updates migrations to use context.
Log Store (RDB)
framework/logstore/{rdb.go,postgres.go,sqlite.go,store.go,config.go,tables.go}
Adds Postgres support, new RDBLogStore with ctx-based APIs, fixes SQLite DSN, migrates interfaces to context, adjusts table types (non-pointer slices), and search/flush/close operations.
Streaming Framework
framework/streaming/{accumulator.go,types.go,chat.go,audio.go,transcription.go}
Adds a streaming Accumulator with per-request chunk pools, processing for chat/audio/transcription streams, response conversion, TTL cleanup, and helpers to detect/process streaming responses.
Core Runtime & Providers
core/bifrost.go, core/utils.go, core/providers/*, core/changelog.md, core/version
Switches plugins to atomic pointer for hot-reload, adds RemovePlugin/ReloadPlugin, updates post-hook pipeline, minor provider fixes, and bumps version.
Core Schemas (Pointers→Slices, New Chat/Responses types)
core/schemas/{chatcompletions.go,responses.go,textcompletions.go,bifrost.go,utils.go,providers/**}
Refactors many public fields from pointer-to-slice to slices, adds rich chat/response types (content blocks, tools), updates marshal/unmarshal, adds context keys, and aligns provider adapters. Removes SafeExtractStringSlicePointer.
Pricing
framework/pricing/{main.go,sync.go}
Makes pricing init/sync context-aware, adds background worker cancellation, model-provider mappings, and helper accessors.
Plugins — OTEL (New)
plugins/otel/*
Adds OTLP plugin with HTTP/gRPC clients, converter to ResourceSpans, TTL map, config/module files, and docker-compose for observability stack.
Plugins — Logging
plugins/logging/{main.go,operations.go,utils.go,pool.go,streaming.go,go.mod,version,changelog.md}
Refactors to ctx-based APIs, integrates new streaming accumulator, removes old streaming pools, updates data types, and adapts operations to processed stream responses.
Plugins — Governance
plugins/governance/{main.go,resolver.go,store.go,tracker.go,version,changelog.md}
Adds per-instance context, makes store/tracker ctx-aware, adapts model/provider checks to provider configs, and updates budget/rate limit flows with ctx.
Plugins — Others
plugins/{maxim/*,semanticcache/*,telemetry/*,jsonparser/*,mocker/*}
Converts Init/configs to pointer-based, adds ctx where needed, renames fields, adds cleanup, updates tests, bumps deps/versions, aligns context key usage.
HTTP Middleware Tests & Server Tests
transports/bifrost-http/handlers/{middlewares_test.go,server_test.go}
Adds tests for CORS/middleware chaining and for generic plugin config marshaling; includes benchmarks.
Transport Config & Modules
transports/config.schema.json, transports/go.mod, framework/go.mod, plugins/*/go.mod, framework/docker-compose.yml
Extends schema for Postgres stores and plugin array; bumps dependencies; adds Postgres docker-compose.
UI — Observability & Enterprise
ui/app/observability/**/*, ui/app/_fallbacks/enterprise/**/*, ui/components/sidebar.tsx, ui/lib/constants/config.ts
Adds Observability UI (OTEL/Maxim forms), enterprise fallback views, updates sidebar with new entries, introduces IS_ENTERPRISE flag.
UI — Governance & VK
ui/app/virtual-keys/**/*, ui/app/teams-customers/**/*, ui/app/governance/views/virtual-key-dialog.tsx (removed)
Splits VK page, removes old VK dialog, updates details view to show keys and provider configs; restructures teams/customers layout and budget displays.
UI — Components & Logs
ui/components/ui/{input.tsx,select.tsx,table.tsx}, ui/app/logs/views/*
Enhances Input with copy button, SelectItem disables with reason, Table adds containerClassName, adjusts logs table layout/typography and conditional rendering.
Misc & Tests
tests/**, .gitignore, Makefile, transports/changelog.md, framework/*/changelog.md, plugins/*/changelog.md, tests/configs/*
Adds new test configs (with Postgres, observability), updates ignore rules and Makefile target, and changelogs.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Main as main.go
  participant Server as BifrostHTTPServer
  participant Cfg as Config (lib)
  participant Store as ConfigStore (RDB)
  participant Router as HTTP Router
  participant UI as UI Handler

  Main->>Server: NewBifrostHTTPServer(version, uiFS)
  Main->>Server: Bootstrap(ctx)
  activate Server
  Server->>Cfg: LoadConfig(ctx)
  Cfg->>Store: NewConfigStore(ctx, config)
  Store-->>Cfg: instance
  Server->>Server: InitializeTelemetry()
  Server->>Router: RegisterRoutes(ctx, middlewares...)
  Server->>UI: RegisterUIHandler(middlewares...)
  deactivate Server
  Main->>Server: Start()
  Server-->>Main: serve + graceful shutdown on signals
Loading
sequenceDiagram
  autonumber
  participant Client as HTTP Client
  participant MW as VKProviderRoutingMiddleware
  participant Store as ConfigStore
  participant Bifrost as Core Bifrost
  participant Provider as Model Provider

  Client->>MW: POST /api/chat (x-bf-vk, model)
  MW->>Store: GetVirtualKeyByValue(ctx, vk)
  Store-->>MW: VK + ProviderConfigs
  MW->>MW: Filter configs by allowed models
  MW->>MW: Weighted-select provider/model
  MW->>Bifrost: Forward request (rewritten model)
  Bifrost->>Provider: Execute request
  Provider-->>Bifrost: Response/Stream
  Bifrost-->>Client: Response/Stream
Loading
sequenceDiagram
  autonumber
  participant Core as Bifrost
  participant Acc as Streaming Accumulator
  participant Log as Logging Plugin
  participant OTEL as OTEL Plugin

  Core->>Acc: ProcessStreamingResponse(ctx, chunk)
  Acc-->>Core: Delta/Final ProcessedStreamResponse
  opt Delta
    Core->>Log: PostHook(ctx, delta)
    Core->>OTEL: PostHook(ctx, delta)
  end
  opt Final
    Acc->>Acc: Assemble complete message, usage, cost
    Core->>Log: PostHook(ctx, final)
    Core->>OTEL: PostHook(ctx, final)
  end
Loading
sequenceDiagram
  autonumber
  participant API as /api/plugins
  participant PH as PluginsHandler
  participant Store as ConfigStore
  participant Core as Bifrost
  API->>PH: PUT /plugins/{name} (config, enabled)
  PH->>Store: UpdatePlugin(ctx, name, config, enabled)
  Store-->>PH: OK
  PH->>Core: ReloadPlugin(ctx, name, config)
  alt Reload OK
    Core-->>PH: OK
    PH-->>API: 200
  else Reload Fail
    Core-->>PH: error
    PH-->>API: 200 (partial success message)
  end
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~180+ minutes

Poem

In burrows of code, I twitch an ear,
New routes hum softly, Postgres draws near.
Streams I gather, chunk by chunk,
OTEL drums trace, no packets sunk.
Keys choose paths by weighted chance—
I thump approval: hot-reload dance.
Hop! The dashboards prance. 🐇✨

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Description Check ⚠️ Warning The description follows the repository template headings but materially understates and mislabels the PR scope: it emphasizes the HTTP server refactor while omitting or under‑representing major additions (the new OTEL plugin, dynamic plugin reload/remove API, extensive UI additions, context-aware store/postgres support, and many plugin changes). The "Type of change" box is set only to Refactor but this PR introduces notable features and UI work, and the "Affected areas" checkboxes do not include Plugins or UI despite those changes. The "How to test" section is minimal and should include explicit steps for testing plugin reload/remove, OTEL emitters (HTTP/gRPC), migrations/postgres, and verifying UI pages. Update the PR description to fully reflect the scope: list all major features (OTEL plugin, dynamic plugin reloads/removes, observability UI, context-aware stores, Postgres support, etc.), mark "Feature" and add "Plugins" and "UI (Next.js)" under Affected areas, and expand "How to test" with concrete steps for plugin lifecycle (Reload/Remove), OTEL emitter verification (HTTP and gRPC), migrations and DB setup (postgres), and UI verification (pages/forms/screenshots). Also call out any migration or compatibility notes (database triggers, config schema changes) and attach screenshots or recordings for UI changes. Keep the summary and checklist aligned with the template so reviewers can validate testing and breaking-change impact.
✅ Passed checks (2 passed)
Check name Status Explanation
Title Check ✅ Passed The title is a single, concise sentence that accurately highlights several primary changes (OTEL plugin, dynamic plugin reloads, UI updates and bug fixes) and is directly related to the large changeset; it is specific enough for a quick scan. Although it enumerates multiple highlights rather than a single dominant change, it remains descriptive and not vague.
Docstring Coverage ✅ Passed Docstring coverage is 92.86% which is sufficient. The required threshold is 80.00%.

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

@akshaydeo akshaydeo marked this pull request as ready for review September 15, 2025 11:01
Copy link
Contributor Author

akshaydeo commented Sep 15, 2025

Warning

This pull request is not mergeable via GitHub because a downstack PR is open. Once all requirements are satisfied, merge this PR as a stack on Graphite.
Learn more

This stack of pull requests is managed by Graphite. Learn more about stacking.

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

🧹 Nitpick comments (9)
transports/bifrost-http/handlers/handlers.go (1)

13-16: Guard against nil logger assignment (and ensure it’s always set before use).

If SetLogger(nil) is called (or never called in tests), any logger.* call in this package will panic.

Apply this diff:

 func SetLogger(l schemas.Logger) {
-	logger = l
+	if l == nil {
+		return
+	}
+	logger = l
 }

Please confirm all entry points (including tests/benchmarks) call handlers.SetLogger(...) before Bootstrap(); otherwise we should add a package‑local no‑op logger as a safe default.

framework/configstore/tables.go (1)

123-123: DB default for MaxRequestBodySizeMB: confirm migration semantics and backward compat.

Tag gorm:"default:100" sets a DB default for new rows; existing rows with 0 remain 0. Given fasthttp.Server.MaxRequestBodySize uses this value, verify legacy configs don’t unintentionally become “unlimited” or too restrictive.

  • Ensure AutoMigrate adds the default to the column on supported DBs.
  • Decide semantics for 0 (unlimited vs. invalid). If invalid, clamp or backfill during load.
transports/bifrost-http/handlers/server.go (7)

84-86: Avoid hard‑coded default path; use the constant.

Use DefaultAppDir instead of "./bifrost-data" for consistency.

Apply this diff:

-	if appDir != "" && appDir != "./bifrost-data" {
+	if appDir != "" && appDir != DefaultAppDir {
 		return appDir
 	}

128-150: CORS: add Vary: Origin to prevent cache poisoning across origins.

Responses can be cached and served to a different origin without Vary.

Apply this diff:

 	return func(ctx *fasthttp.RequestCtx) {
 		origin := string(ctx.Request.Header.Peek("Origin"))
 
 		// Check if origin is allowed (localhost always allowed + configured origins)
 		if IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins) {
 			ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
 		}
+		ctx.Response.Header.Add("Vary", "Origin")

214-221: UI: add X-Content-Type-Options: nosniff.

Small hardening to prevent MIME sniffing.

Apply this diff:

 	ctx.SetContentType(contentType)
+	ctx.Response.Header.Set("X-Content-Type-Options", "nosniff")

342-345: Redundant plugin.Enabled check.

Already filtered at Lines 321–323.

Apply this diff:

-		case semanticcache.PluginName:
-			if !plugin.Enabled {
-				logger.Debug("semantic cache plugin is disabled, skipping initialization")
-				continue
-			}
+		case semanticcache.PluginName:

448-450: Validate/clamp request body size to avoid accidental unlimited or overflow.

Protect against <=0 (unlimited?) and absurdly large sizes.

Apply this diff:

-	s.server = &fasthttp.Server{
-		Handler:            corsHandler,
-		MaxRequestBodySize: config.ClientConfig.MaxRequestBodySizeMB * 1024 * 1024,
-	}
+	sizeMB := config.ClientConfig.MaxRequestBodySizeMB
+	if sizeMB <= 0 {
+		sizeMB = 100 // safe default; matches DB default
+	}
+	const maxAllowedMB = 1024 // 1 GiB cap; adjust as needed
+	if sizeMB > maxAllowedMB {
+		logger.Warn("max request body size clamped from %dMB to %dMB", sizeMB, maxAllowedMB)
+		sizeMB = maxAllowedMB
+	}
+	s.server = &fasthttp.Server{
+		Handler:            corsHandler,
+		MaxRequestBodySize: sizeMB * 1024 * 1024,
+	}

465-469: Startup log message is misleading on bind failure.

Log “starting” before ListenAndServe; success can be implied afterwards or by lack of error.

Apply this diff:

-		logger.Info("successfully started bifrost, serving UI on http://%s:%s", s.Host, s.Port)
+		logger.Info("starting bifrost, serving UI on http://%s:%s", s.Host, s.Port)

471-505: Optional: honor context cancellation in addition to OS signals.

Allows callers/tests to stop the server via ctx.

Apply this diff:

 	// Wait for either termination signal or server error
 	select {
-	case sig := <-sigChan:
+	case sig := <-sigChan:
 		logger.Info("received signal %v, initiating graceful shutdown...", sig)
+	case <-ctx.Done():
+		logger.Info("context cancelled, initiating graceful shutdown...")
 		// Create shutdown context with timeout
 		shutdownCtx, cancel := context.WithTimeout(ctx, 30*time.Second)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c6170cd and 5e9747a.

📒 Files selected for processing (4)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/main.go (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (3)
transports/bifrost-http/handlers/handlers.go (1)
core/schemas/logger.go (1)
  • Logger (28-55)
transports/bifrost-http/handlers/server.go (17)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (26-34)
  • NewWebSocketHandler (37-46)
transports/bifrost-http/handlers/utils.go (2)
  • IsOriginAllowed (74-82)
  • SendError (27-36)
framework/configstore/clientconfig.go (1)
  • ClientConfig (28-38)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
transports/bifrost-http/lib/account.go (1)
  • NewBaseAccount (20-24)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
plugins/logging/main.go (1)
  • LoggerPlugin (117-132)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (28-34)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
transports/bifrost-http/main.go (3)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (46-61)
  • DefaultHost (38-38)
  • NewBifrostHTTPServer (64-75)
  • DefaultPort (39-39)
  • DefaultAppDir (40-40)
  • DefaultLogLevel (41-41)
  • DefaultLogOutputStyle (42-42)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • LoggerOutputType (17-17)
transports/bifrost-http/handlers/handlers.go (1)
  • SetLogger (14-16)
🔇 Additional comments (4)
transports/bifrost-http/handlers/handlers.go (1)

3-6: Logger DI entrypoint looks good.

Clear, minimal surface for wiring a logger into handlers.

framework/configstore/tables.go (1)

788-791: Trigger DDL whitespace tweak — no behavioral change.

Looks fine.

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

121-124: Logger flag wiring LGTM.

Level and output style are applied before server bootstrap; handlers logger is injected.


111-112: Sane host fallback.

Defaults to handlers.DefaultHost when BIFROST_HOST is empty.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 5e9747a to c697a9b Compare September 15, 2025 11:23
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 (1)
transports/bifrost-http/main.go (1)

115-121: Shadowing bug resolved—now assigning to the package-level server.

This fixes the earlier nil deref risk at server.Bootstrap().

🧹 Nitpick comments (5)
transports/bifrost-http/handlers/init.go (1)

13-16: Guard against nil logger injection.

A nil logger will panic at first use. Add a defensive check.

 func SetLogger(l schemas.Logger) {
-	logger = l
+	if l == nil {
+		panic("handlers.SetLogger: nil logger")
+	}
+	logger = l
 }
transports/bifrost-http/main.go (4)

87-88: Prevent panic for long Version strings.

strings.Repeat panics on negative counts if Version exceeds the line width. Clamp padding.

-	versionLine := fmt.Sprintf("║%s%s%s║", strings.Repeat(" ", (61-2-len(Version))/2), Version, strings.Repeat(" ", (61-2-len(Version)+1)/2))
+	width := 61 - 2
+	pad := width - len(Version)
+	if pad < 0 {
+		pad = 0
+	}
+	left := pad / 2
+	right := pad - left
+	versionLine := fmt.Sprintf("║%s%s%s║", strings.Repeat(" ", left), Version, strings.Repeat(" ", right))

123-126: Validate log flags before applying.

If an invalid value is passed, the logger may end up with an undefined level/style. Add validation with fallback to defaults.

 	flag.Parse()
-	// Configure logger from flags
+	// Validate and normalize logger flags before configuring
+	switch schemas.LoggerOutputType(server.LogOutputStyle) {
+	case schemas.LoggerOutputTypeJSON, schemas.LoggerOutputTypePretty:
+	default:
+		fmt.Printf("warning: invalid -log-style=%q; defaulting to %s\n", server.LogOutputStyle, handlers.DefaultLogOutputStyle)
+		server.LogOutputStyle = handlers.DefaultLogOutputStyle
+	}
+	switch schemas.LogLevel(server.LogLevel) {
+	case schemas.LogLevelDebug, schemas.LogLevelInfo, schemas.LogLevelWarn, schemas.LogLevelError:
+	default:
+		fmt.Printf("warning: invalid -log-level=%q; defaulting to %s\n", server.LogLevel, handlers.DefaultLogLevel)
+		server.LogLevel = handlers.DefaultLogLevel
+	}
+	// Configure logger from flags
 	logger.SetOutputType(schemas.LoggerOutputType(server.LogOutputStyle))
 	logger.SetLevel(schemas.LogLevel(server.LogLevel))

54-65: Graceful shutdown with OS signals.

Use a signal-aware context so the server can shut down cleanly on SIGINT/SIGTERM.

 import (
 	"context"
 	"embed"
 	"flag"
 	"fmt"
 	"os"
+	"os/signal"
 	"strings"
+	"syscall"
 
 	bifrost "github.com/maximhq/bifrost/core"
 	schemas "github.com/maximhq/bifrost/core/schemas"
 	"github.com/maximhq/bifrost/transports/bifrost-http/handlers"
 )
-	ctx := context.Background()
+	ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM)
+	defer stop()

Also applies to: 132-132


133-141: Error logging path is fine; consider Fatal if it exits.

If the logger’s Fatal handles exit, you can replace Error+os.Exit(1) with Fatal for consistency. Optional.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5e9747a and c697a9b.

📒 Files selected for processing (5)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • framework/configstore/tables.go
  • transports/bifrost-http/handlers/server.go
🧰 Additional context used
🧬 Code graph analysis (2)
transports/bifrost-http/handlers/init.go (1)
core/schemas/logger.go (1)
  • Logger (28-55)
transports/bifrost-http/main.go (3)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (46-64)
  • DefaultHost (38-38)
  • NewBifrostHTTPServer (67-78)
  • DefaultPort (39-39)
  • DefaultAppDir (40-40)
  • DefaultLogLevel (41-41)
  • DefaultLogOutputStyle (42-42)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • LoggerOutputType (17-17)
transports/bifrost-http/handlers/init.go (1)
  • SetLogger (14-16)
🔇 Additional comments (1)
transports/bifrost-http/main.go (1)

112-121: Flag defaults and env override look good.

Using BIFROST_HOST as the default with explicit flag overrides is clear and predictable.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from c697a9b to b21428b Compare September 15, 2025 11: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

♻️ Duplicate comments (2)
transports/bifrost-http/main.go (1)

115-121: Shadowing fix confirmed — good catch.

You now assign to the package-level server instead of creating a new local with :=. This removes the nil-deref risk when calling server.Bootstrap()/Start().

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

363-371: Don’t Fatal on semantic cache config (graceful degrade).

Marshalling/unmarshalling errors for an optional plugin shouldn’t crash the server. Log and continue.

-				configBytes, err := json.Marshal(plugin.Config)
-				if err != nil {
-					logger.Fatal("failed to marshal semantic cache config: %v", err)
-				}
-				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-					logger.Fatal("failed to unmarshal semantic cache config: %v", err)
-				}
+				configBytes, err := json.Marshal(plugin.Config)
+				if err != nil {
+					logger.Error("failed to marshal semantic cache config: %v", err)
+					continue
+				}
+				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
+					logger.Error("failed to unmarshal semantic cache config: %v", err)
+					continue
+				}
🧹 Nitpick comments (3)
transports/bifrost-http/handlers/server.go (3)

131-152: CORS: add Vary and echo requested headers for robust preflight.

Add Vary headers to keep caches correct and reflect Access-Control-Request-Headers to avoid preflight failures with custom headers.

 return func(ctx *fasthttp.RequestCtx) {
   origin := string(ctx.Request.Header.Peek("Origin"))
+  // Help caches and proxies
+  ctx.Response.Header.Add("Vary", "Origin")
+  ctx.Response.Header.Add("Vary", "Access-Control-Request-Method")
+  ctx.Response.Header.Add("Vary", "Access-Control-Request-Headers")
@@
-  ctx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
+  // Echo requested headers when provided; fall back to a safe default
+  if reqH := string(ctx.Request.Header.Peek("Access-Control-Request-Headers")); reqH != "" {
+    ctx.Response.Header.Set("Access-Control-Allow-Headers", reqH)
+  } else {
+    ctx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With, Accept")
+  }

465-468: Set server timeouts to harden against slowloris and stuck clients.

Fasthttp defaults leave timeouts unset. Add sane timeouts.

 s.server = &fasthttp.Server{
   Handler:            corsHandler,
   MaxRequestBodySize: s.config.ClientConfig.MaxRequestBodySizeMB * 1024 * 1024,
+  ReadTimeout:        30 * time.Second,
+  WriteTimeout:       2 * time.Minute,
+  IdleTimeout:        2 * time.Minute,
+  // Optional hardening:
+  // MaxRequestsPerConn: 0, // unlimited; set if needed
+  // ReduceMemoryUsage:  true,
 }

473-476: Improve startup log for 0.0.0.0/IPv6 listeners.

Log both the bind address and a friendly URL (localhost) when binding to all interfaces.

-		logger.Info("successfully started bifrost, serving UI on http://%s:%s", s.Host, s.Port)
+		uiHost := s.Host
+		if s.Host == "0.0.0.0" || s.Host == "::" {
+			uiHost = "localhost"
+		}
+		logger.Info("successfully started bifrost (listening on %s). UI: http://%s:%s", serverAddr, uiHost, s.Port)
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c697a9b and b21428b.

📒 Files selected for processing (5)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • transports/bifrost-http/handlers/init.go
  • framework/configstore/tables.go
🧰 Additional context used
🧬 Code graph analysis (2)
transports/bifrost-http/main.go (3)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (46-64)
  • DefaultHost (38-38)
  • NewBifrostHTTPServer (67-77)
  • DefaultPort (39-39)
  • DefaultAppDir (40-40)
  • DefaultLogLevel (41-41)
  • DefaultLogOutputStyle (42-42)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • LoggerOutputType (17-17)
transports/bifrost-http/handlers/init.go (1)
  • SetLogger (9-11)
transports/bifrost-http/handlers/server.go (15)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (26-34)
  • NewWebSocketHandler (37-46)
transports/bifrost-http/handlers/utils.go (1)
  • IsOriginAllowed (74-82)
framework/configstore/clientconfig.go (2)
  • ClientConfig (28-38)
  • GovernanceConfig (54-60)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (28-34)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
🔇 Additional comments (3)
transports/bifrost-http/main.go (2)

124-127: Logger DI into handlers is clean.

Flag-driven level/output applied before handlers.SetLogger(logger) keeps server internals decoupled from main.


133-141: Error paths look correct.

Bootstrap/start errors are logged and exit with non‑zero status.

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

255-256: No action required — version is a package-level var.

Declaration found at transports/bifrost-http/handlers/init.go:5 (var version string). The version = s.Version assignment in server.go writes to that package-level variable; no shadowing in handlers.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch 2 times, most recently from 692bee5 to 73cf382 Compare September 16, 2025 19:16
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/server.go (3)

88-91: Honor provided appDir and use the DefaultAppDir constant (don’t special‑case "./bifrost-data").

Treating "./bifrost-data" as “not provided” diverges from the flag’s displayed default and can break setups. Only fall back when appDir == "" and avoid hardcoded strings.

Apply:

-	if appDir != "" && appDir != "./bifrost-data" {
+	if appDir != "" {
 		return appDir
 	}
@@
-	if configDir == "" {
-		configDir = "./bifrost-data"
-	}
+	if configDir == "" {
+		configDir = DefaultAppDir
+	}

Also applies to: 114-116


321-329: Don’t Fatal on semantic cache config errors; log and skip the plugin.

A misconfigured optional plugin shouldn’t crash the server.

Apply:

-				configBytes, err := json.Marshal(plugin.Config)
-				if err != nil {
-					logger.Fatal("failed to marshal semantic cache config: %v", err)
-				}
-				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-					logger.Fatal("failed to unmarshal semantic cache config: %v", err)
-				}
+				configBytes, err := json.Marshal(plugin.Config)
+				if err != nil {
+					logger.Error("failed to marshal semantic cache config: %v", err)
+					continue
+				}
+				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
+					logger.Error("failed to unmarshal semantic cache config: %v", err)
+					continue
+				}

382-385: Don’t Fatal on governance handler init; log and skip routes.

Governance is optional; avoid taking down the server on handler construction failure.

Apply:

-		if err != nil {
-			logger.Fatal("failed to initialize governance handler: %v", err)
-		}
+		if err != nil {
+			logger.Error("failed to initialize governance handler: %v", err)
+			governanceHandler = nil
+		}
🧹 Nitpick comments (7)
transports/bifrost-http/handlers/server.go (7)

134-151: CORS: set Allow‑Credentials only when origin is allowed; add Vary: Origin; use method constant.

Prevents misleading headers and improves cache correctness.

Apply:

 	return func(ctx *fasthttp.RequestCtx) {
 		origin := string(ctx.Request.Header.Peek("Origin"))
 
-		// Check if origin is allowed (localhost always allowed + configured origins)
-		if IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins) {
-			ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
-		}
+		allowed := IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins)
+		if allowed && origin != "" {
+			ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
+			ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
+			ctx.Response.Header.Add("Vary", "Origin")
+		} else {
+			ctx.Response.Header.Del("Access-Control-Allow-Credentials")
+		}
 
 		ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
 		ctx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
-		ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
 		ctx.Response.Header.Set("Access-Control-Max-Age", "86400")
 
 		// Handle preflight OPTIONS requests
-		if string(ctx.Method()) == "OPTIONS" {
+		if string(ctx.Method()) == fasthttp.MethodOptions {
 			ctx.SetStatusCode(fasthttp.StatusOK)
 			return
 		}

243-244: Guard against nil router in RegisterRoute.

If RegisterRoute is called before initializeRoutes, this will panic.

Apply:

 func (s *BifrostHTTPServer) RegisterRoute(method string, path string, handler fasthttp.RequestHandler) {
-	s.router.Handle(method, path, handler)
+	if s.router == nil {
+		s.router = router.New()
+	}
+	s.router.Handle(method, path, handler)
 }

510-513: Add sensible server timeouts and clamp MaxRequestBodySize.

Prevents slowloris and protects memory. Keep values configurable later.

Apply:

-	s.server = &fasthttp.Server{
-		Handler:            corsHandler,
-		MaxRequestBodySize: s.config.ClientConfig.MaxRequestBodySizeMB * 1024 * 1024,
-	}
+	mb := s.config.ClientConfig.MaxRequestBodySizeMB
+	if mb <= 0 {
+		mb = 100 // fallback to 100MB
+	}
+	s.server = &fasthttp.Server{
+		Handler:            corsHandler,
+		MaxRequestBodySize: mb * 1024 * 1024,
+		ReadTimeout:        30 * time.Second,
+		WriteTimeout:       30 * time.Second,
+		IdleTimeout:        120 * time.Second,
+	}

518-521: Log “starting” before ListenAndServe; report success after bind.

Avoids claiming success if bind fails.

Apply:

-		logger.Info("successfully started bifrost, serving UI on http://%s:%s", s.Host, s.Port)
-		if err := s.server.ListenAndServe(serverAddr); err != nil {
+		logger.Info("starting bifrost on http://%s:%s ...", s.Host, s.Port)
+		if err := s.server.ListenAndServe(serverAddr); err != nil {
 			errChan <- err
 		}

503-507: Stop signal notifications on exit to avoid leaks.

Small hygiene improvement.

Apply:

 	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	defer signal.Stop(sigChan)

190-194: Avoid echoing request paths in 404 bodies (reduce info leakage).

Return a generic message; details are already in logs.

Apply:

-			ctx.SetStatusCode(fasthttp.StatusNotFound)
-			ctx.SetBodyString("404 - Static asset not found: " + requestPath)
+			ctx.SetStatusCode(fasthttp.StatusNotFound)
+			ctx.SetBodyString("404 - Static asset not found")
-			ctx.SetStatusCode(fasthttp.StatusNotFound)
-			ctx.SetBodyString("404 - File not found")
+			ctx.SetStatusCode(fasthttp.StatusNotFound)
+			ctx.SetBodyString("404 - File not found")

Also applies to: 213-216


125-129: Use errors.As to detect AlreadyRegisteredError.

Type assertion may miss non-identical concrete types; errors.As is more robust.

Apply:

-	if err := prometheus.Register(collector); err != nil {
-		if _, ok := err.(prometheus.AlreadyRegisteredError); !ok {
-			logger.Error("failed to register prometheus collector: %v", err)
-		}
-	}
+	if err := prometheus.Register(collector); err != nil {
+		var are prometheus.AlreadyRegisteredError
+		if !errors.As(err, &are) {
+			logger.Error("failed to register prometheus collector: %v", err)
+		}
+	}

Note: add errors import.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b21428b and 73cf382.

📒 Files selected for processing (5)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • transports/bifrost-http/handlers/init.go
  • framework/configstore/tables.go
  • transports/bifrost-http/main.go
🧰 Additional context used
🧬 Code graph analysis (1)
transports/bifrost-http/handlers/server.go (8)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • Logger (28-55)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (26-34)
  • NewWebSocketHandler (37-46)
core/bifrost.go (1)
  • Bifrost (33-48)
transports/bifrost-http/handlers/utils.go (1)
  • IsOriginAllowed (74-82)
framework/configstore/clientconfig.go (2)
  • ClientConfig (28-38)
  • GovernanceConfig (54-60)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
transports/bifrost-http/lib/account.go (1)
  • NewBaseAccount (20-24)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
🔇 Additional comments (1)
transports/bifrost-http/handlers/server.go (1)

456-465: OK — package 'version' present and logger set before use

var version and SetLogger are defined in transports/bifrost-http/handlers/init.go; main.init calls handlers.SetLogger(logger) in transports/bifrost-http/main.go before server.Bootstrap and there are no handlers package init() functions that log.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 73cf382 to d2f95cc Compare September 17, 2025 09:49
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

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

85-87: Honor any provided app-dir and use constants (no hardcoded "./bifrost-data").

- if appDir != "" && appDir != "./bifrost-data" {
+ if appDir != "" {
   return appDir
 }
@@
- if configDir == "" {
-   configDir = "./bifrost-data"
- }
+ if configDir == "" {
+   configDir = DefaultAppDir
+ }

Also applies to: 111-113


174-181: Don’t Fatal on logging plugin init; degrade gracefully and guard usage.

- loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
- if err != nil {
-   logger.Fatal("failed to initialize logging plugin: %v", err)
- }
- plugins = append(plugins, loggingPlugin)
+ loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
+ if err != nil {
+   logger.Error("failed to initialize logging plugin: %v", err)
+ } else {
+   plugins = append(plugins, loggingPlugin)
+ }

235-241: Don’t Fatal on semantic cache config marshal/unmarshal; skip plugin.

- configBytes, err := json.Marshal(plugin.Config)
- if err != nil {
-   logger.Fatal("failed to marshal semantic cache config: %v", err)
- }
- if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-   logger.Fatal("failed to unmarshal semantic cache config: %v", err)
- }
+ configBytes, err := json.Marshal(plugin.Config)
+ if err != nil {
+   logger.Error("failed to marshal semantic cache config: %v", err)
+   continue
+ }
+ if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
+   logger.Error("failed to unmarshal semantic cache config: %v", err)
+   continue
+ }
🧹 Nitpick comments (7)
transports/bifrost-http/handlers/ui.go (4)

26-30: Support HEAD for UI routes (no response body).

Browsers and CDNs issue HEADs; serve headers without a body.

 func (h *UIHandler) RegisterRoutes(router *router.Router) {
   router.GET("/", h.serveDashboard)
   router.GET("/{filepath:*}", h.serveDashboard)
+  router.HEAD("/", h.serveDashboard)
+  router.HEAD("/{filepath:*}", h.serveDashboard)
 }

And near the send step:

-  // Send the file content
-  ctx.SetBody(data)
+  // Send the file content (suppress body for HEAD)
+  if string(ctx.Method()) == "HEAD" {
+    ctx.Response.Header.SetContentLength(len(data))
+    return
+  }
+  ctx.SetBody(data)

58-59: Use filepath.Ext to detect extensions (handles edge cases).

-  // Check if this is a static asset request (has file extension)
-  hasExtension := strings.Contains(filepath.Base(cleanPath), ".")
+  // Check if this is a static asset request (has file extension)
+  ext := filepath.Ext(cleanPath)
+  hasExtension := ext != ""

Then remove the later re‑declaration of ext:

-  // Set content type based on file extension
-  ext := filepath.Ext(cleanPath)
+  // Set content type based on file extension

64-71: Set Content-Type on 404s to avoid defaulting to text/html.

   if hasExtension {
     ctx.SetStatusCode(fasthttp.StatusNotFound)
-    ctx.SetBodyString("404 - Static asset not found: " + requestPath)
+    ctx.SetContentType("text/plain; charset=utf-8")
+    ctx.SetBodyString("404 - Static asset not found")
     return
   }

88-91: Consistent 404 body and type for non-asset misses.

   } else {
     ctx.SetStatusCode(fasthttp.StatusNotFound)
-    ctx.SetBodyString("404 - File not found")
+    ctx.SetContentType("text/plain; charset=utf-8")
+    ctx.SetBodyString("404 - File not found")
     return
   }
transports/bifrost-http/handlers/server.go (3)

135-148: CORS: add Vary: Origin and include Accept in allowed headers.

   if IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins) {
     ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
+    ctx.Response.Header.Add("Vary", "Origin")
   }
-  ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
-  ctx.Response.Header.Set("Access-Control-Allow-Headers", "Content-Type, Authorization, X-Requested-With")
+  ctx.Response.Header.Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS")
+  ctx.Response.Header.Set("Access-Control-Allow-Headers", "Accept, Content-Type, Authorization, X-Requested-With")
   ctx.Response.Header.Set("Access-Control-Allow-Credentials", "true")
   ctx.Response.Header.Set("Access-Control-Max-Age", "86400")

317-331: Register UI catch-all last to avoid accidental shadowing of future routes.

- uiHandler.RegisterRoutes(s.router)
 ...
- // Add Prometheus /metrics endpoint
- s.router.GET("/metrics", fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler()))
+ // Add Prometheus /metrics endpoint early
+ s.router.GET("/metrics", fasthttpadaptor.NewFastHTTPHandler(promhttp.Handler()))
+ // Register UI routes last (catch-all)
+ uiHandler.RegisterRoutes(s.router)

Although fasthttp/router prefers static routes over wildcards, placing the catch‑all last reduces risk as routes evolve.


121-127: Robust AlreadyRegistered check (pointer-safe).

- if err := prometheus.Register(collector); err != nil {
-   if _, ok := err.(prometheus.AlreadyRegisteredError); !ok {
-     logger.Error("failed to register prometheus collector: %v", err)
-   }
- }
+ if err := prometheus.Register(collector); err != nil {
+   var are prometheus.AlreadyRegisteredError
+   if !errors.As(err, &are) {
+     logger.Error("failed to register prometheus collector: %v", err)
+   }
+ }

(Remember to import errors.)

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 73cf382 and d2f95cc.

📒 Files selected for processing (6)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/handlers/ui.go (1 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • transports/bifrost-http/handlers/init.go
  • framework/configstore/tables.go
🧰 Additional context used
🧬 Code graph analysis (3)
transports/bifrost-http/handlers/server.go (14)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (26-34)
  • NewWebSocketHandler (37-46)
transports/bifrost-http/handlers/utils.go (2)
  • IsOriginAllowed (74-82)
  • SendError (27-36)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (29-35)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
transports/bifrost-http/handlers/ui.go (1)
  • NewUIHandler (20-24)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
transports/bifrost-http/main.go (3)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (45-64)
  • DefaultHost (37-37)
  • NewBifrostHTTPServer (67-77)
  • DefaultPort (38-38)
  • DefaultAppDir (39-39)
  • DefaultLogLevel (40-40)
  • DefaultLogOutputStyle (41-41)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • LoggerOutputType (17-17)
transports/bifrost-http/handlers/init.go (1)
  • SetLogger (9-11)
transports/bifrost-http/handlers/ui.go (1)
transports/bifrost-http/handlers/server.go (1)
  • RegisterRoutes (273-337)
🔇 Additional comments (3)
transports/bifrost-http/main.go (2)

73-73: Good fix: avoid shadowing the package-level server.


130-142: Bootstrap/Start error handling looks solid.

Logs and exits cleanly on failures.

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

359-359: Confirmed: package-level version variable exists — no action required.
Found var version string in transports/bifrost-http/handlers/init.go (line 5).

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from d2f95cc to edaffe9 Compare September 17, 2025 13:09
cursor[bot]

This comment was marked as outdated.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from edaffe9 to 7f08f2d Compare September 17, 2025 13:17
cursor[bot]

This comment was marked as outdated.

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

♻️ Duplicate comments (4)
transports/bifrost-http/handlers/server.go (4)

83-116: Honor provided app-dir (including "./bifrost-data"); don’t special‑case the default.

Treating the default as “not provided” silently relocates config to OS dirs and diverges from flag help. Use provided value when non‑empty; only fall back when empty. Also, use DefaultAppDir constant for the final fallback.

 func GetDefaultConfigDir(appDir string) string {
 	// If appDir is provided, use it directly
-	if appDir != "" && appDir != "./bifrost-data" {
+	if appDir != "" {
 		return appDir
 	}
@@
 	// If we couldn't determine the config directory, fall back to current directory
 	if configDir == "" {
-		configDir = "./bifrost-data"
+		configDir = DefaultAppDir
 	}

167-174: Don’t Fatal on logging plugin init; degrade gracefully.

A misconfigured optional plugin shouldn’t crash the server. Log and skip instead; guard subsequent usage (already nil‑checked via loggerPlugin != nil).

 	if config.ClientConfig.EnableLogging && config.LogsStore != nil {
 		// Use dedicated logs database with high-scale optimizations
 		loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
 		if err != nil {
-			logger.Fatal("failed to initialize logging plugin: %v", err)
-		}
-		plugins = append(plugins, loggingPlugin)
+			logger.Error("failed to initialize logging plugin: %v", err)
+		} else {
+			plugins = append(plugins, loggingPlugin)
+		}
 	}

225-235: Don’t Fatal on semantic cache config marshal/unmarshal; skip plugin and continue.

Crashing here takes down the whole server for an optional plugin.

 			if plugin.Config != nil {
 				configBytes, err := json.Marshal(plugin.Config)
 				if err != nil {
-					logger.Fatal("failed to marshal semantic cache config: %v", err)
+					logger.Error("failed to marshal semantic cache config: %v", err)
+					continue
 				}
 				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-					logger.Fatal("failed to unmarshal semantic cache config: %v", err)
+					logger.Error("failed to unmarshal semantic cache config: %v", err)
+					continue
 				}
 			}

4-31: Restore signal handling; current code never subscribes to SIGINT/SIGTERM.

signal.Notify is commented out and imports are missing, so graceful shutdown won’t trigger.

 import (
 	"context"
 	"embed"
 	"encoding/json"
 	"fmt"
 	"net"
 	"os"
+	"os/signal"
 	"path/filepath"
 	"runtime"
 	"strings"
+	"syscall"
 	"time"
@@
-	// Initializing server		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	// Watch for termination signals
+	signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)

Also applies to: 416-420

🧹 Nitpick comments (5)
transports/bifrost-http/handlers/ui.go (2)

57-59: Fix extension detection to support dotfiles like "/.well-known".

strings.Contains(filepath.Base(cleanPath), ".") misclassifies directories starting with a dot as “files with extension”, preventing SPA fallback. Use filepath.Ext instead.

-	// Check if this is a static asset request (has file extension)
-	hasExtension := strings.Contains(filepath.Base(cleanPath), ".")
+	// Check if this is a static asset request (has file extension)
+	hasExtension := filepath.Ext(cleanPath) != ""

94-101: Add MIME fallbacks for JS/CSS/SVG/JSON to avoid octet-stream.

mime.TypeByExtension can return empty for common types in some environments; JS served as octet-stream breaks execution.

 	ext := filepath.Ext(cleanPath)
 	contentType := mime.TypeByExtension(ext)
 	if contentType == "" {
-		contentType = "application/octet-stream"
+		switch ext {
+		case ".js":
+			contentType = "application/javascript"
+		case ".css":
+			contentType = "text/css; charset=utf-8"
+		case ".svg":
+			contentType = "image/svg+xml"
+		case ".json":
+			contentType = "application/json"
+		case ".txt", ".map":
+			contentType = "text/plain; charset=utf-8"
+		default:
+			contentType = "application/octet-stream"
+		}
 	}
 	ctx.SetContentType(contentType)
transports/bifrost-http/handlers/server.go (3)

398-403: Check RegisterRoutes error before registering UI.

If routing init fails, we shouldn’t proceed to register UI, which can leave partial state.

-	err = s.RegisterRoutes(s.ctx)
-	// Register UI handler
-	s.RegisterUIHandler()
-	if err != nil {
-		return fmt.Errorf("failed to initialize routes: %v", err)
-	}
+	err = s.RegisterRoutes(s.ctx)
+	if err != nil {
+		return fmt.Errorf("failed to initialize routes: %v", err)
+	}
+	// Register UI handler last
+	s.RegisterUIHandler()

128-151: Add Vary: Origin to CORS responses.

When echoing Origin, add Vary to prevent cache misdelivery across different origins.

 func CorsMiddleware(config *lib.Config, next fasthttp.RequestHandler) fasthttp.RequestHandler {
 	return func(ctx *fasthttp.RequestCtx) {
 		origin := string(ctx.Request.Header.Peek("Origin"))
 
+		// Ensure caches vary by Origin
+		ctx.Response.Header.Add("Vary", "Origin")
+
 		// Check if origin is allowed (localhost always allowed + configured origins)
 		if IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins) {
 			ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
 		}

289-297: Comment mismatch about WebSocket vs UI ordering.

Code registers WS routes before UI (UI is registered in Bootstrap after routes). Update the comment to reflect actual ordering to avoid future regressions.

-	// Websocket handler needs to go below UI handler
+	// Websocket handler is registered before UI; UI is registered last in Bootstrap
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between edaffe9 and 7f08f2d.

📒 Files selected for processing (7)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/handlers/ui.go (1 hunks)
  • transports/bifrost-http/handlers/websocket.go (4 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • framework/configstore/tables.go
  • transports/bifrost-http/handlers/init.go
  • transports/bifrost-http/handlers/websocket.go
🧰 Additional context used
🧬 Code graph analysis (2)
transports/bifrost-http/handlers/server.go (18)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
transports/bifrost-http/handlers/utils.go (1)
  • IsOriginAllowed (74-82)
framework/configstore/clientconfig.go (2)
  • ClientConfig (28-38)
  • GovernanceConfig (54-60)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (27-36)
  • NewWebSocketHandler (39-49)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (29-35)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
transports/bifrost-http/handlers/ui.go (1)
  • NewUIHandler (20-24)
transports/bifrost-http/handlers/init.go (1)
  • SetVersion (14-16)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
transports/bifrost-http/lib/account.go (1)
  • NewBaseAccount (20-24)
transports/bifrost-http/main.go (3)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (43-63)
  • DefaultHost (35-35)
  • NewBifrostHTTPServer (66-76)
  • DefaultPort (36-36)
  • DefaultAppDir (37-37)
  • DefaultLogLevel (38-38)
  • DefaultLogOutputStyle (39-39)
core/schemas/logger.go (2)
  • LogLevel (6-6)
  • LoggerOutputType (17-17)
transports/bifrost-http/handlers/init.go (1)
  • SetLogger (9-11)
🔇 Additional comments (2)
transports/bifrost-http/handlers/ui.go (1)

26-30: Confirm UI routes are registered last.

Catch‑all GET “/{filepath:*}” must be registered after API routes to avoid shadowing. Server.RegisterUIHandler appears to do this; please keep that invariant.

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

113-121: LGTM: server instance bound correctly; shadowing bug resolved.

Global server is initialized once and flags bind to the same instance. Bootstrap/Start flow reads clean.

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 7f08f2d to 76c79e0 Compare September 17, 2025 18:14
@akshaydeo akshaydeo mentioned this pull request Sep 17, 2025
18 tasks
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 (7)
transports/bifrost-http/handlers/init.go (1)

13-16: Duplicate version source — remove global version/SetVersion or prove it’s still needed.

You now pass Version on the server struct and also mirror it into a package-global. Prefer a single source of truth to avoid divergence. If unused, delete the global and the setter; otherwise document why both are necessary.

Apply:

-var version string
@@
-// SetVersion sets the version for the application.
-func SetVersion(v string) {
-	version = v
-}

And remove the SetVersion call in server bootstrap (see server.go fix below).

transports/bifrost-http/handlers/server.go (6)

165-174: Don’t Fatal on optional logging plugin init; degrade gracefully.

A failed optional plugin shouldn’t crash the server. Log and continue.

-		loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
-		if err != nil {
-			logger.Fatal("failed to initialize logging plugin: %v", err)
-		}
-		plugins = append(plugins, loggingPlugin)
+		loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
+		if err != nil {
+			logger.Error("failed to initialize logging plugin: %v", err)
+		} else {
+			plugins = append(plugins, loggingPlugin)
+		}

227-237: Don’t Fatal on semantic cache config (un)marshal. Log and skip plugin.

Crashing here is disproportionate; continue startup without this plugin.

 			if plugin.Config != nil {
 				configBytes, err := json.Marshal(plugin.Config)
 				if err != nil {
-					logger.Fatal("failed to marshal semantic cache config: %v", err)
+					logger.Error("failed to marshal semantic cache config: %v", err)
+					break
 				}
 				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-					logger.Fatal("failed to unmarshal semantic cache config: %v", err)
+					logger.Error("failed to unmarshal semantic cache config: %v", err)
+					break
 				}
 			}

366-366: Remove SetVersion call after consolidating version source.

If you drop the global, this call should go too.

-	SetVersion(s.Version)
+	// Version is available on s.Version; avoid global duplication.

83-88: Honor provided appDir; don’t treat "./bifrost-data" as “not provided”.

Current logic overrides an explicit default value with OS path, changing behavior.

-	if appDir != "" && appDir != "./bifrost-data" {
+	if appDir != "" {
 		return appDir
 	}

418-421: Graceful shutdown: signal.Notify is commented; add imports and enable it.

Without this, SIGINT/SIGTERM won’t be handled.

 import (
 	"context"
 	"embed"
 	"encoding/json"
 	"fmt"
 	"net"
 	"os"
+	"os/signal"
 	"path/filepath"
 	"runtime"
 	"strings"
 	"time"
+	"syscall"
@@
-	// Initializing server		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	// Initialize signal handling for graceful shutdown
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	defer signal.Stop(sigChan)

197-215: Maxim plugin ignores plugin.Config → breaking change; support both config and env.

Prefer values from plugin.Config when provided; fall back to env.

-		case maxim.PluginName:
-			if os.Getenv("MAXIM_LOG_REPO_ID") == "" {
-				logger.Warn("maxim log repo id is required to initialize maxim plugin")
-				continue
-			}
-			if os.Getenv("MAXIM_API_KEY") == "" {
-				logger.Warn("maxim api key is required in environment variable MAXIM_API_KEY to initialize maxim plugin")
-				continue
-			}
-			maximPlugin, err := maxim.Init(maxim.Config{
-				ApiKey:    os.Getenv("MAXIM_API_KEY"),
-				LogRepoId: os.Getenv("MAXIM_LOG_REPO_ID"),
-			})
+		case maxim.PluginName:
+			var mcfg maxim.Config
+			// Prefer explicit config when present
+			if plugin.Config != nil {
+				if b, err := json.Marshal(plugin.Config); err != nil {
+					logger.Warn("maxim: failed to marshal config (falling back to env): %v", err)
+				} else if err := json.Unmarshal(b, &mcfg); err != nil {
+					logger.Warn("maxim: failed to unmarshal config (falling back to env): %v", err)
+				}
+			}
+			// Env fallbacks
+			if mcfg.ApiKey == "" {
+				mcfg.ApiKey = os.Getenv("MAXIM_API_KEY")
+			}
+			if mcfg.LogRepoId == "" {
+				mcfg.LogRepoId = os.Getenv("MAXIM_LOG_REPO_ID")
+			}
+			// Validate required fields
+			if mcfg.ApiKey == "" || mcfg.LogRepoId == "" {
+				logger.Warn("maxim: api key and log repo id are required; skipping initialization")
+				continue
+			}
+			maximPlugin, err := maxim.Init(mcfg)
 			if err != nil {
 				logger.Warn("failed to initialize maxim plugin: %v", err)
 			} else {
 				plugins = append(plugins, maximPlugin)
 			}
🧹 Nitpick comments (6)
transports/bifrost-http/handlers/websocket.go (1)

224-226: Fix misleading log message on ctx cancel.

This is the WS heartbeat goroutine, not the “webserver”.

-				h.logger.Info("got context cancel(), stopping webserver")
+				h.logger.Info("context canceled; stopping websocket heartbeat")
transports/bifrost-http/handlers/server.go (5)

109-112: Use the declared default constant instead of hardcoded string.

-	if configDir == "" {
-		configDir = "./bifrost-data"
-	}
+	if configDir == "" {
+		configDir = DefaultAppDir
+	}

217-221: Redundant if !plugin.Enabled inside case.

Already filtered above; remove dead branch.

-		case semanticcache.PluginName:
-			if !plugin.Enabled {
-				logger.Debug("semantic cache plugin is disabled, skipping initialization")
-				continue
-			}
+		case semanticcache.PluginName:

400-405: Return on routing init error before registering UI.

Avoid partially-initialized router state.

-	err = s.RegisterRoutes(s.ctx)
-	// Register UI handler
-	s.RegisterUIHandler()
-	if err != nil {
+	err = s.RegisterRoutes(s.ctx)
+	if err != nil {
 		return fmt.Errorf("failed to initialize routes: %v", err)
 	}
+	// Register UI handler
+	s.RegisterUIHandler()

428-439: Prefer ShutdownWithContext if available.

If your fasthttp version supports it, honor shutdownCtx timeout.

-		if err := s.Server.Shutdown(); err != nil {
+		if err := s.Server.Shutdown(); err != nil { // or: s.Server.ShutdownWithContext(shutdownCtx)
 			logger.Error("error during graceful shutdown: %v", err)
 		} else {
 			logger.Info("server gracefully shutdown")
 		}

128-151: CORS: add Vary: Origin for cache correctness.

Prevents intermediaries from serving cached responses across origins.

 		if IsOriginAllowed(origin, config.ClientConfig.AllowedOrigins) {
 			ctx.Response.Header.Set("Access-Control-Allow-Origin", origin)
 		}
+		ctx.Response.Header.Add("Vary", "Origin")
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7f08f2d and 76c79e0.

📒 Files selected for processing (7)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/handlers/ui.go (1 hunks)
  • transports/bifrost-http/handlers/websocket.go (4 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • framework/configstore/tables.go
  • transports/bifrost-http/handlers/ui.go
  • transports/bifrost-http/main.go
🧰 Additional context used
🧬 Code graph analysis (3)
transports/bifrost-http/handlers/init.go (1)
core/schemas/logger.go (1)
  • Logger (28-55)
transports/bifrost-http/handlers/websocket.go (2)
plugins/logging/utils.go (1)
  • LogManager (11-20)
core/schemas/logger.go (1)
  • Logger (28-55)
transports/bifrost-http/handlers/server.go (19)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
core/bifrost.go (1)
  • Bifrost (33-48)
transports/bifrost-http/handlers/utils.go (1)
  • IsOriginAllowed (74-82)
framework/configstore/clientconfig.go (2)
  • ClientConfig (28-38)
  • GovernanceConfig (54-60)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (27-36)
  • NewWebSocketHandler (39-49)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (29-35)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
transports/bifrost-http/handlers/ui.go (1)
  • NewUIHandler (20-24)
transports/bifrost-http/handlers/init.go (1)
  • SetVersion (14-16)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-732)
transports/bifrost-http/lib/account.go (1)
  • NewBaseAccount (20-24)
🔇 Additional comments (1)
transports/bifrost-http/handlers/init.go (1)

5-11: SetLogger is invoked in main — no immediate nil-panic risk. handlers.SetLogger(logger) is called at transports/bifrost-http/main.go:127.

This was referenced Sep 20, 2025
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 76c79e0 to 98ed454 Compare September 24, 2025 09:31
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

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

57-58: Consider using path/filepath.Ext for consistency.

While this check works, using filepath.Ext() would be more idiomatic and handle edge cases better.

-// Check if this is a static asset request (has file extension)
-hasExtension := strings.Contains(filepath.Base(cleanPath), ".")
+// Check if this is a static asset request (has file extension)
+hasExtension := filepath.Ext(cleanPath) != ""
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 76c79e0 and 98ed454.

📒 Files selected for processing (7)
  • framework/configstore/tables.go (2 hunks)
  • transports/bifrost-http/handlers/handlers.go (0 hunks)
  • transports/bifrost-http/handlers/init.go (1 hunks)
  • transports/bifrost-http/handlers/server.go (1 hunks)
  • transports/bifrost-http/handlers/ui.go (1 hunks)
  • transports/bifrost-http/handlers/websocket.go (4 hunks)
  • transports/bifrost-http/main.go (3 hunks)
💤 Files with no reviewable changes (1)
  • transports/bifrost-http/handlers/handlers.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • framework/configstore/tables.go
  • transports/bifrost-http/handlers/init.go
🧰 Additional context used
🧬 Code graph analysis (3)
transports/bifrost-http/handlers/server.go (17)
core/schemas/logger.go (4)
  • LogLevelInfo (11-11)
  • LoggerOutputTypeJSON (21-21)
  • LogLevel (6-6)
  • Logger (28-55)
core/bifrost.go (1)
  • Bifrost (33-48)
transports/bifrost-http/handlers/utils.go (2)
  • IsOriginAllowed (74-82)
  • SendError (27-36)
framework/configstore/clientconfig.go (2)
  • ClientConfig (28-38)
  • GovernanceConfig (54-60)
transports/bifrost-http/handlers/logging.go (2)
  • LoggingHandler (19-22)
  • NewLoggingHandler (25-30)
transports/bifrost-http/handlers/governance.go (2)
  • GovernanceHandler (20-25)
  • NewGovernanceHandler (28-39)
transports/bifrost-http/handlers/cache.go (2)
  • CacheHandler (10-13)
  • NewCacheHandler (15-25)
transports/bifrost-http/handlers/websocket.go (2)
  • WebSocketHandler (27-36)
  • NewWebSocketHandler (39-49)
transports/bifrost-http/handlers/providers.go (1)
  • NewProviderHandler (29-35)
transports/bifrost-http/handlers/completions.go (1)
  • NewCompletionHandler (33-39)
transports/bifrost-http/handlers/mcp.go (1)
  • NewMCPHandler (24-30)
transports/bifrost-http/handlers/integrations.go (1)
  • NewIntegrationHandler (23-36)
transports/bifrost-http/handlers/config.go (1)
  • NewConfigHandler (26-32)
transports/bifrost-http/handlers/plugins.go (1)
  • NewPluginsHandler (19-24)
plugins/telemetry/setup.go (1)
  • InitPrometheusMetrics (57-175)
transports/bifrost-http/lib/config.go (1)
  • LoadConfig (164-737)
transports/bifrost-http/lib/account.go (1)
  • NewBaseAccount (20-24)
transports/bifrost-http/main.go (2)
transports/bifrost-http/handlers/server.go (7)
  • BifrostHTTPServer (43-63)
  • DefaultHost (35-35)
  • NewBifrostHTTPServer (66-76)
  • DefaultPort (36-36)
  • DefaultAppDir (37-37)
  • DefaultLogLevel (38-38)
  • DefaultLogOutputStyle (39-39)
transports/bifrost-http/handlers/init.go (1)
  • SetLogger (9-11)
transports/bifrost-http/handlers/websocket.go (2)
plugins/logging/utils.go (1)
  • LogManager (11-20)
core/schemas/logger.go (1)
  • Logger (28-55)
🔇 Additional comments (18)
transports/bifrost-http/handlers/websocket.go (3)

28-28: LGTM!

The addition of the context field to the WebSocketHandler struct enables proper lifecycle management and context cancellation.


39-39: LGTM!

The constructor now properly accepts a context parameter, which aligns with the new context-aware design pattern.


224-226: LGTM!

The context cancellation handling is properly implemented. The log message clearly indicates the shutdown reason and the function returns immediately upon context cancellation.

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

19-24: LGTM!

Simple constructor that properly initializes the UIHandler with the embedded file system.


102-109: Good cache strategy implementation.

The cache headers are appropriately set: long-term caching for static assets in _next/static/, no-cache for HTML files to ensure fresh content, and moderate caching for other assets.


32-48: Verify Next.js RSC .txt/index.txt mapping.
Repo search found no Next.js/RSC references; the only .txt-specific handling is in transports/bifrost-http/handlers/ui.go (lines 32–48). Confirm this special-case is intentional and document/add tests or remove if unnecessary.

transports/bifrost-http/main.go (4)

73-73: LGTM!

The package-level server variable is now properly declared as a pointer to BifrostHTTPServer.


115-115: LGTM!

The server is now properly initialized using assignment (=) rather than declaration (:=), which correctly assigns to the package-level variable instead of shadowing it.


124-127: LGTM!

The logger configuration is now properly driven by the server's properties, and the logger is correctly set up in the handlers package.


133-142: LGTM!

The main function is now properly streamlined, delegating all startup responsibilities to the server's Bootstrap and Start methods. Error handling is clear and appropriate.

transports/bifrost-http/handlers/server.go (8)

42-63: LGTM!

The BifrostHTTPServer struct is well-designed with clear separation of concerns. All necessary fields are present for managing the server lifecycle, configuration, and dependencies.


217-220: Redundant disabled plugin check.

The outer loop already filters out disabled plugins, so the inner check if !plugin.Enabled is redundant.

Remove the redundant check:

 		case semanticcache.PluginName:
-			if !plugin.Enabled {
-				logger.Debug("semantic cache plugin is disabled, skipping initialization")
-				continue
-			}

292-299: Ensure WebSocket handler is properly guarded.

The code creates a WebSocket handler only when the logging plugin is available, which is correct. The heartbeat is started immediately, which aligns with the server lifecycle.


65-76: Constructor lacks logger parameter, causing potential nil panics.

The constructor doesn't accept or initialize a logger, but many methods in this file use the package-global logger variable. This creates a dependency on external initialization and potential nil pointer panics.

Consider updating the constructor to accept a logger parameter:

-func NewBifrostHTTPServer(version string, uiContent embed.FS) *BifrostHTTPServer {
+func NewBifrostHTTPServer(version string, uiContent embed.FS, logger schemas.Logger) *BifrostHTTPServer {
+	SetLogger(logger) // Set the package-global logger immediately
 	return &BifrostHTTPServer{
 		Version:        version,
 		UIContent:      uiContent,
+		Logger:         logger, // Store on struct for future use
 		Port:           DefaultPort,
 		Host:           DefaultHost,
 		AppDir:         DefaultAppDir,
 		LogLevel:       DefaultLogLevel,
 		LogOutputStyle: DefaultLogOutputStyle,
 	}
 }

84-87: Default app-dir logic inconsistency.

The logic treats the literal "./bifrost-data" string as if the flag was not provided, which is inconsistent with the flag's displayed default and could break existing setups.

Apply this fix:

-	if appDir != "" && appDir != "./bifrost-data" {
+	if appDir != "" {
 		return appDir
 	}

And at line 111:

-	if configDir == "" {
-		configDir = "./bifrost-data"
-	}
+	if configDir == "" {
+		configDir = DefaultAppDir
+	}

169-172: Logging plugin initialization should degrade gracefully.

The code currently calls logger.Fatal() when logging plugin initialization fails, which crashes the server. This should degrade gracefully since logging is optional.

Apply this fix:

-		loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
-		if err != nil {
-			logger.Fatal("failed to initialize logging plugin: %v", err)
-		}
-		plugins = append(plugins, loggingPlugin)
+		loggingPlugin, err = logging.Init(logger, config.LogsStore, pricingManager)
+		if err != nil {
+			logger.Error("failed to initialize logging plugin: %v", err)
+		} else {
+			plugins = append(plugins, loggingPlugin)
+		}

230-236: Semantic cache config marshaling should not Fatal.

Using logger.Fatal() for optional plugin configuration errors will crash the entire server. This should degrade gracefully.

Apply this fix:

 				configBytes, err := json.Marshal(plugin.Config)
 				if err != nil {
-					logger.Fatal("failed to marshal semantic cache config: %v", err)
+					logger.Error("failed to marshal semantic cache config: %v", err)
+					continue
 				}
 				if err := json.Unmarshal(configBytes, &semCacheConfig); err != nil {
-					logger.Fatal("failed to unmarshal semantic cache config: %v", err)
+					logger.Error("failed to unmarshal semantic cache config: %v", err)
+					continue
 				}

418-420: Missing signal.Notify call prevents graceful shutdown.

The comment on line 420 shows that signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) is commented out or missing, which means the server won't handle termination signals properly. The required imports os/signal and syscall are also missing.

Based on standard Go signal handling patterns, add the missing imports and signal notification:

 import (
 	"context"
 	"embed"
 	"encoding/json"
 	"fmt"
 	"net"
 	"os"
+	"os/signal"
 	"path/filepath"
 	"runtime"
 	"strings"
+	"syscall"
 	"time"
 	// Create channels for signal and error handling
 	sigChan := make(chan os.Signal, 1)
 	errChan := make(chan error, 1)
-	// Initializing server		signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)
+	// Register for signal notifications
+	signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM)

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 98ed454 to ac5abfd Compare September 24, 2025 21:52
@akshaydeo akshaydeo changed the base branch from main to graphite-base/470 September 24, 2025 21:58
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 21675b2 to e41d9ec Compare October 5, 2025 22:38
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from e41d9ec to 5f6b769 Compare October 6, 2025 00:09
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: 15

Caution

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

⚠️ Outside diff range comments (2)
ui/app/teams-customers/views/customerDialog.tsx (1)

184-245: Remove duplicate budget sections
Two blocks under isEditing && customer?.budget (lines 184–218 and 220–245) render the same budget information twice. Consolidate into a single section or remove the redundant block.

core/bifrost.go (1)

1246-1263: Preserve post-hook order with preCount.

pluginCount := len(*bifrost.plugins.Load()) re-reads the global slice after queuing. If a concurrent reload removes the plugin before the response arrives, len(*bifrost.plugins.Load()) becomes smaller and RunPostHooks runs with runFrom == 0, so the plugin that already ran a PreHook never gets its PostHook. Pass the original preCount here (and in the streaming error branch) so we always unwind exactly the hooks that ran.

-	var resp *schemas.BifrostResponse
-	pluginCount := len(*bifrost.plugins.Load())
+	var resp *schemas.BifrostResponse
 	select {
 	case result = <-msg.Response:
 		latency := time.Since(startTime).Milliseconds()
 		if result.ExtraFields.Latency == nil {
 			result.ExtraFields.Latency = Ptr(float64(latency))
 		}
-		resp, bifrostErr := pipeline.RunPostHooks(&ctx, result, nil, pluginCount)
+		resp, bifrostErr := pipeline.RunPostHooks(&ctx, result, nil, preCount)
@@
 	case bifrostErrVal := <-msg.Err:
 		bifrostErrPtr := &bifrostErrVal
-		resp, bifrostErrPtr = pipeline.RunPostHooks(&ctx, nil, bifrostErrPtr, pluginCount)
+		resp, bifrostErrPtr = pipeline.RunPostHooks(&ctx, nil, bifrostErrPtr, preCount)
@@
-		recoveredResp, recoveredErr := pipeline.RunPostHooks(&ctx, nil, &bifrostErrVal, len(*bifrost.plugins.Load()))
+		recoveredResp, recoveredErr := pipeline.RunPostHooks(&ctx, nil, &bifrostErrVal, preCount)

Also applies to: 1370-1376

♻️ Duplicate comments (17)
ui/app/logs/views/speechView.tsx (1)

95-97: AudioPlayer renders with empty src during streaming.

This change removes the conditional rendering check for isStreaming, causing the AudioPlayer to always render when the Speech Output section is visible. When isStreaming is true but speechOutput is null or speechOutput.audio is empty, the player receives an empty string as the src, which may cause display issues or errors despite the error boundary.

This concern was previously flagged in the existing review comment.

plugins/maxim/main.go (1)

454-459: Guard against nil bifrostErr.Error before dereferencing.

Line 453 checks if bifrostErr != nil, but lines 455-457 dereference bifrostErr.Error fields without verifying that bifrostErr.Error itself is non-nil. If bifrostErr.Error is nil, this will panic.

Apply this diff to add the missing nil check:

-	if bifrostErr != nil {
+	if bifrostErr != nil && bifrostErr.Error != nil {
 		genErr := logging.GenerationError{
 			Message: bifrostErr.Error.Message,
 			Code:    bifrostErr.Error.Code,
 			Type:    bifrostErr.Error.Type,
 		}
 		logger.SetGenerationError(generationID, &genErr)

Alternatively, if bifrostErr can have a nil Error field but you still want to log something, construct a safe fallback GenerationError using available data (e.g., bifrostErr.Message or a default message).

ui/app/_fallbacks/enterprise/views/contactUsView.tsx (1)

31-31: Mark decorative icon as hidden from screen readers.

The ArrowUpRight icon should be marked as decorative to prevent screen readers from announcing it separately, as suggested in a previous review.

Apply this diff:

-						Read more <ArrowUpRight className="text-muted-foreground h-3 w-3" />
+						Read more <ArrowUpRight className="text-muted-foreground h-3 w-3" aria-hidden="true" />
plugins/logging/changelog.md (1)

4-4: Remove duplicate "Fix:"

Line 4 repeats "Fix:" twice. This was flagged in a previous review.

Apply this diff:

-- Fix: Fix capturing Bifrost-specific errors in logs (e.g. provider not configured)
+- Fix: Capture Bifrost-specific errors in logs (e.g., provider not configured)
transports/bifrost-http/handlers/middlewares_test.go (1)

27-28: Rebind loop variable to avoid closure capture issue.

The subtest closure captures the loop variable origin, causing all subtests to execute with the final slice value instead of the intended per-iteration value.

Rebind the variable before creating the subtest:

 	for _, origin := range localhostOrigins {
+		origin := origin // rebind for closure
 		t.Run(origin, func(t *testing.T) {

Alternatively, pass the value as a parameter to an immediately-invoked function.

transports/config.schema.json (1)

607-614: Allow IPv6 collector endpoints.

collector_url still uses ^[^:\\s]+:\\d+$, so [::1]:4317 and other bracketed IPv6 hosts fail validation. Please widen the pattern as previously suggested.

-                        {
-                          "pattern": "^[^:\\s]+:\\d+$"
-                        }
+                        {
+                          "pattern": "^(\\[[0-9A-Fa-f:.%]+\\]|[^\\[\\]:\\s]+):\\d+$"
+                        }
transports/bifrost-http/handlers/plugins.go (1)

190-207: Return 500 on real lookup errors.

The fallback branch treats every non-ErrNotFound error as a 404, masking DB/connection failures and incorrectly telling clients the plugin is missing. Only respond with 404 when the not-found sentinel matches; otherwise propagate a 500.

-		} else {
-			h.logger.Error("failed to get plugin: %v", err)
-			SendError(ctx, 404, "Plugin not found", h.logger)
-			return
-		}
+		} else {
+			h.logger.Error("failed to get plugin: %v", err)
+			SendError(ctx, fasthttp.StatusInternalServerError, "Failed to retrieve plugin", h.logger)
+			return
+		}
framework/go.mod (1)

15-16: LGTM! Dependency upgrades aligned.

The upgrades to gorm v1.31.0, addition of Postgres driver v1.6.0, and updates to gRPC/protobuf dependencies are consistent across modules. Per the past review comment, ensure go mod tidy is run to sync go.sum.

Based on learnings

Also applies to: 91-96

plugins/logging/pool.go (1)

20-24: Restore StreamUpdateData reset to avoid stale pooled state.

putLogMessage now leaves msg.StreamUpdateData untouched. Because log messages are pooled, reusing an instance with a non-nil StreamUpdateData leaks the previous request’s stream payload into the next caller. We still need to nil that pointer when returning the message to the pool.

Apply this diff to clear the field:

 	// Don't reset UpdateData and StreamUpdateData here since they're returned
 	// to their own pools in the defer function - just clear the pointers
-	msg.UpdateData = nil	
+	msg.UpdateData = nil
+	msg.StreamUpdateData = nil
plugins/otel/grpc.go (1)

19-23: Default to TLS (or require explicit opt-in for insecure transport).

The gRPC client still dials with insecure.NewCredentials(), so every OTLP export runs unencrypted. This was already flagged earlier and remains unresolved. Please make TLS the default (system roots or configured certs) and only fall back to insecure when an explicit “allow_insecure” config is set.

-import (
-	"context"
-
-	collectorpb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
-	"google.golang.org/grpc"
-	"google.golang.org/grpc/credentials/insecure"
-)
+import (
+	"context"
+	"crypto/tls"
+
+	collectorpb "go.opentelemetry.io/proto/otlp/collector/trace/v1"
+	"google.golang.org/grpc"
+	"google.golang.org/grpc/credentials"
+	"google.golang.org/grpc/credentials/insecure"
+)
@@
-func NewOtelClientGRPC(endpoint string) (*OtelClientGRPC, error) {
-	conn, err := grpc.NewClient(endpoint, grpc.WithTransportCredentials(insecure.NewCredentials()))
+func NewOtelClientGRPC(endpoint string, allowInsecure bool) (*OtelClientGRPC, error) {
+	creds := grpc.WithTransportCredentials(credentials.NewTLS(&tls.Config{}))
+	if allowInsecure {
+		creds = grpc.WithTransportCredentials(insecure.NewCredentials())
+	}
+
+	conn, err := grpc.NewClient(endpoint, creds)

(call sites must pass the new flag/config and choose allowInsecure only for trusted local setups.)

ui/app/observability/fragments/otelFormFragment.tsx (1)

181-184: Reset must keep otel_config in a valid shape.

otelFormSchema requires otel_config, but the Reset handler sets it to undefined, immediately invalidating the form and blocking users from saving defaults. Reset should restore the schema-compliant defaults instead of wiping the object.

-								form.reset({
-									enabled: false,
-									otel_config: undefined,
-								});
+								form.reset({
+									enabled: false,
+									otel_config: {
+										collector_url: "",
+										trace_type: "otel",
+										protocol: "http",
+									},
+								});
framework/pricing/main.go (1)

108-110: Use a background context for the sync worker

context.WithCancel(ctx) still ties the long-lived worker to the init context, so any cancellation of the caller (startup, CLI, tests) tears down the sync loop immediately. Please derive the worker context from context.Background() (optionally wrapped in your own cancel) before launching the goroutine.

framework/streaming/accumulator.go (1)

279-293: Return pooled chunks through the reset helpers

Inside Cleanup we’re pushing cached chunks straight back into the pools via chatStreamChunkPool.Put / audioStreamChunkPool.Put / transcriptionStreamChunkPool.Put. Those methods bypass the zeroing logic in put{Type}StreamChunk, so prior request state (cache debug, cost, errors, etc.) bleeds into the next borrower. Please funnel everything through the helper so each chunk is scrubbed before reuse.

Suggested fix:

-		for _, chunk := range accumulator.ChatStreamChunks {
-			a.chatStreamChunkPool.Put(chunk)
-		}
-		for _, chunk := range accumulator.TranscriptionStreamChunks {
-			a.transcriptionStreamChunkPool.Put(chunk)
-		}
-		for _, chunk := range accumulator.AudioStreamChunks {
-			a.audioStreamChunkPool.Put(chunk)
-		}
+		for _, chunk := range accumulator.ChatStreamChunks {
+			a.putChatStreamChunk(chunk)
+		}
+        for _, chunk := range accumulator.TranscriptionStreamChunks {
+			a.putTranscriptionStreamChunk(chunk)
+		}
+        for _, chunk := range accumulator.AudioStreamChunks {
+			a.putAudioStreamChunk(chunk)
+		}
plugins/governance/main.go (2)

203-206: Fix virtual key lookup in PostHook.

ContextKey(schemas.BifrostContextKeyVirtualKeyHeader) creates a different key type than the one used to populate the context, so virtualKey comes back empty and the usage update is skipped. That breaks rate-limit/budget tracking for every request. Please query the context with the same schemas.BifrostContextKeyVirtualKeyHeader value you already use in PreHook.

-	virtualKey := getStringFromContext(*ctx, ContextKey(schemas.BifrostContextKeyVirtualKeyHeader))
+	virtualKey := getStringFromContext(*ctx, schemas.BifrostContextKeyVirtualKeyHeader)

299-301: Avoid canceling usage updates during shutdown.

Cleanup() calls p.cancelFunc() before the goroutine completes, so every in-flight UpdateUsage sees a cancelled context and drops the write—usage, rate limits, and budgets all go stale. Detach these writes from the plugin’s cancellable context (give them their own background/timeout context) so they can flush even while shutdown proceeds.

-	p.tracker.UpdateUsage(p.ctx, usageUpdate)
+	updateCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
+	defer cancel()
+	p.tracker.UpdateUsage(updateCtx, usageUpdate)

Add the corresponding time import at the top of the file.

plugins/logging/main.go (1)

333-339: Do not bubble logging failures back to the caller.

PostHook still returns the accumulator error, so a transient logging failure aborts the user request. Log and continue instead; keep the hook non-blocking.

-		streamResponse, err := p.accumulator.ProcessStreamingResponse(ctx, result, bifrostErr)
+		streamResponse, err := p.accumulator.ProcessStreamingResponse(ctx, result, bifrostErr)
 		if err != nil {
-			p.logger.Error("failed to process streaming response: %v", err)
-			return result, bifrostErr, err
+			p.logger.Error("failed to process streaming response: %v", err)
+			return result, bifrostErr, nil
 		}
transports/bifrost-http/lib/config.go (1)

544-555: Keep auto-detected providers when seeding the DB.

When configData.Providers is nil we auto-detect into config.Providers, but the subsequent UpdateProvidersConfig call still writes the original (empty) processedProviders map, wiping the detected providers from the store.

Assign the detected providers before persisting, e.g.

-		if config.ConfigStore != nil {
+		if config.ConfigStore != nil {
 			logger.Debug("updating providers config in store")
-			err = config.ConfigStore.UpdateProvidersConfig(ctx, processedProviders)
+			if len(processedProviders) == 0 {
+				processedProviders = config.Providers
+			}
+			err = config.ConfigStore.UpdateProvidersConfig(ctx, processedProviders)
🧹 Nitpick comments (6)
ui/app/logs/views/logsTable.tsx (1)

81-82: Consider extracting the magic value for maintainability.

The value calc(100vh-20rem) is hardcoded in two places. If this constraint needs adjustment in the future, both locations would require updates.

Consider extracting this to a constant or CSS variable:

const TABLE_MAX_HEIGHT = "max-h-[calc(100vh-20rem)]";

Then use:

-<div className="rounded-sm border max-h-[calc(100vh-20rem)]">
-  <Table containerClassName="max-h-[calc(100vh-20rem)]">
+<div className={`rounded-sm border ${TABLE_MAX_HEIGHT}`}>
+  <Table containerClassName={TABLE_MAX_HEIGHT}>
transports/bifrost-http/handlers/middlewares_test.go (1)

19-25: Add IPv6 localhost and HTTPS 0.0.0.0 test cases.

The localhost origins list is missing common development variants:

  • http://[::1]:<port> (IPv6 localhost HTTP)
  • https://[::1]:<port> (IPv6 localhost HTTPS)
  • https://0.0.0.0:<port> (HTTPS on all interfaces)

Add these test cases to ensure comprehensive localhost origin coverage:

 	localhostOrigins := []string{
 		"http://localhost:3000",
 		"https://localhost:3000",
 		"http://127.0.0.1:8080",
 		"http://0.0.0.0:5000",
 		"https://127.0.0.1:3000",
+		"http://[::1]:3000",
+		"https://[::1]:3000",
+		"https://0.0.0.0:5000",
 	}

Ensure that the isLocalhostOrigin helper in transports/bifrost-http/handlers/utils.go treats these origins as local.

core/providers/utils.go (1)

135-166: Match skip list against canonical header names.

skipHeaders is documented as canonicalized, but the comparison still uses the original key. If extraHeaders contains a lowercase entry (e.g., "x-custom-header"), the skip check breaks because "x-custom-header""X-Custom-Header", so the header slips through. Compare against canonicalKey in both functions to keep the skip list effective.

-		if skipHeaders != nil {
-			if slices.Contains(skipHeaders, key) {
+		if skipHeaders != nil {
+			if slices.Contains(skipHeaders, canonicalKey) {

Apply the same change in setExtraHeadersHTTP.

framework/docker-compose.yml (1)

6-7: Consider externalizing credentials for flexibility.

Hardcoded credentials (bifrost/bifrost_password) are acceptable for local development but consider documenting that these should be overridden via environment variables in shared or production-like environments.

framework/logstore/postgres.go (1)

23-40: Consider validating config fields before use.

The function does not validate that required config fields (Host, Port, User, DBName, SSLMode) are non-empty before attempting to connect. This could lead to obscure connection errors.

Add validation:

if config.Host == "" || config.Port == "" || config.User == "" || config.DBName == "" {
    return nil, fmt.Errorf("missing required Postgres config fields")
}
ui/app/virtual-keys/page.tsx (1)

16-32: Consider consolidating error handling.

The three separate useEffect hooks for error handling could be consolidated into a single effect or a custom hook to reduce code duplication.

Example:

useEffect(() => {
  if (vkError) toast.error(`Failed to load virtual keys: ${getErrorMessage(vkError)}`);
  if (teamsError) toast.error(`Failed to load teams: ${getErrorMessage(teamsError)}`);
  if (customersError) toast.error(`Failed to load customers: ${getErrorMessage(customersError)}`);
}, [vkError, teamsError, customersError]);

@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from v1.3.0 to graphite-base/470 October 6, 2025 04:03
@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from graphite-base/470 to main October 6, 2025 04:03
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 0829918 to 4aba40f Compare October 6, 2025 04:28
@coderabbitai coderabbitai bot requested a review from SamstyleGhost October 6, 2025 04:29
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 4aba40f to 025f8a9 Compare October 6, 2025 04:30
@akshaydeo akshaydeo changed the base branch from main to graphite-base/470 October 6, 2025 04:31
@akshaydeo akshaydeo changed the base branch from graphite-base/470 to v1.3.0 October 6, 2025 04:31
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)
core/schemas/chatcompletions.go (1)

232-243: Clear stale ChatMessageContent state on Unmarshal

Decoding a string payload sets ContentStr but leaves any previous ContentBlocks slice in place (and vice-versa). When the struct is reused—which is common during streaming decodes—you end up with both fields non-nil, so MarshalJSON fails the mutual-exclusion check and callers see stale blocks. Please nil the opposite field in each branch.

Apply this diff:

@@
 	var stringContent string
 	if err := sonic.Unmarshal(data, &stringContent); err == nil {
 		mc.ContentStr = &stringContent
+		mc.ContentBlocks = nil
 		return nil
 	}
@@
 	var arrayContent []ChatContentBlock
 	if err := sonic.Unmarshal(data, &arrayContent); err == nil {
 		mc.ContentBlocks = arrayContent
+		mc.ContentStr = nil
 		return nil
 	}
♻️ Duplicate comments (2)
plugins/otel/go.mod (1)

7-11: Update core dependency to match other modules.

This module requires core v1.1.34 while plugins/logging uses v1.1.37. Align to the latest version used across the repo to prevent API drift.

plugins/otel/converter.go (1)

120-126: Format embedding values as floats, not integers.

Embedding values are floats but are formatted as integers using %d, which truncates decimal values and causes data loss.

Apply this diff:

 	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("%f", v)
 		}
 		params = append(params, kvStr("gen_ai.input.embedding", strings.Join(embedding, ",")))
 	}
🧹 Nitpick comments (1)
ui/app/teams-customers/page.tsx (1)

58-75: Expose active tab state to assistive tech.

Right now the buttons don’t convey which view is selected, so screen readers announce identical controls. Adding a role="tablist" wrapper and aria-pressed (or aria-current) on the active button restores the lost semantics from the old Tabs component.

-			<div className="flex min-w-[200px] flex-col gap-1 rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20">
+			<div
+				className="flex min-w-[200px] flex-col gap-1 rounded-md bg-zinc-50/50 p-4 dark:bg-zinc-800/20"
+				role="tablist"
+				aria-label="Teams and customers views"
+			>
 				{["teams", "customers"].map((tab) => (
 					<button
 						key={tab}
 						className={cn(
 							"mb-1 flex w-full items-center gap-2 rounded-sm border px-3 py-1.5 text-sm",
 							activeTab === tab
 								? "bg-secondary opacity-100 hover:opacity-100"
 								: "hover:bg-secondary cursor-pointer border-transparent opacity-100 hover:border",
 						)}
 						onClick={() => setActiveTab(tab)}
 						type="button"
+						aria-pressed={activeTab === tab}
 					>
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 5f6b769 and 025f8a9.

⛔ Files ignored due to path filters (2)
  • plugins/logging/go.sum is excluded by !**/*.sum
  • plugins/otel/go.sum is excluded by !**/*.sum
📒 Files selected for processing (14)
  • core/schemas/chatcompletions.go (12 hunks)
  • core/schemas/providers/anthropic/chat.go (18 hunks)
  • core/schemas/providers/anthropic/responses.go (21 hunks)
  • core/schemas/providers/bedrock/responses.go (6 hunks)
  • core/schemas/providers/cohere/responses.go (8 hunks)
  • core/schemas/utils.go (0 hunks)
  • plugins/logging/go.mod (2 hunks)
  • plugins/otel/converter.go (1 hunks)
  • plugins/otel/go.mod (1 hunks)
  • tests/core-providers/scenarios/multiple_images.go (1 hunks)
  • tests/core-providers/scenarios/reasoning.go (2 hunks)
  • tests/core-providers/scenarios/response_validation.go (3 hunks)
  • tests/core-providers/scenarios/utils.go (7 hunks)
  • ui/app/teams-customers/page.tsx (2 hunks)
💤 Files with no reviewable changes (1)
  • core/schemas/utils.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • core/schemas/providers/bedrock/responses.go
🧰 Additional context used
🧬 Code graph analysis (9)
tests/core-providers/scenarios/response_validation.go (1)
core/schemas/chatcompletions.go (1)
  • ChatAssistantMessage (296-300)
core/schemas/providers/anthropic/chat.go (2)
core/schemas/providers/anthropic/types.go (5)
  • AnthropicMessageRoleAssistant (52-52)
  • AnthropicContentBlock (116-126)
  • AnthropicContent (62-65)
  • AnthropicMessage (56-59)
  • AnthropicMessageError (273-276)
core/schemas/chatcompletions.go (2)
  • ChatAssistantMessage (296-300)
  • ChatMessageContent (204-207)
tests/core-providers/scenarios/utils.go (2)
core/schemas/chatcompletions.go (2)
  • ChatContentBlock (260-267)
  • ChatAssistantMessage (296-300)
core/schemas/responses.go (1)
  • ResponsesMessageContentBlock (527-538)
ui/app/teams-customers/page.tsx (3)
ui/lib/utils.ts (1)
  • cn (4-6)
ui/app/teams-customers/views/teamsTable.tsx (1)
  • TeamsTable (34-204)
ui/app/teams-customers/views/customerTable.tsx (1)
  • CustomersTable (34-218)
tests/core-providers/scenarios/multiple_images.go (1)
core/schemas/chatcompletions.go (1)
  • ChatContentBlock (260-267)
core/schemas/providers/anthropic/responses.go (3)
core/schemas/utils.go (1)
  • SafeExtractStringSlice (432-460)
core/schemas/responses.go (2)
  • ResponsesMessageContentBlock (527-538)
  • ResponsesFunctionToolCallOutput (860-863)
core/schemas/providers/anthropic/types.go (2)
  • AnthropicContent (62-65)
  • AnthropicContentBlock (116-126)
plugins/otel/converter.go (4)
plugins/otel/types.go (13)
  • KeyValue (21-21)
  • AnyValue (24-24)
  • StringValue (27-27)
  • IntValue (30-30)
  • DoubleValue (33-33)
  • BoolValue (36-36)
  • ArrayValue (39-39)
  • ArrayValueValue (42-42)
  • ListValue (45-45)
  • KeyValueList (48-48)
  • ResourceSpan (9-9)
  • ScopeSpan (12-12)
  • Span (15-15)
core/schemas/bifrost.go (13)
  • BifrostSpeechRequest (152-158)
  • BifrostEmbeddingRequest (144-150)
  • BifrostTextCompletionRequest (120-126)
  • BifrostChatRequest (128-134)
  • BifrostTranscriptionRequest (160-166)
  • BifrostRequest (15-27)
  • RequestType (89-89)
  • TextCompletionRequest (92-92)
  • ChatCompletionRequest (93-93)
  • ChatCompletionStreamRequest (94-94)
  • TranscriptionStreamRequest (101-101)
  • SpeechStreamRequest (99-99)
  • BifrostResponse (177-192)
core/schemas/chatcompletions.go (5)
  • ChatMessageRoleUser (186-186)
  • ChatMessageRoleAssistant (185-185)
  • ChatMessageRoleSystem (187-187)
  • ChatMessageRoleTool (188-188)
  • ChatMessageRoleDeveloper (189-189)
framework/pricing/main.go (1)
  • PricingManager (22-38)
core/schemas/providers/cohere/responses.go (1)
core/schemas/utils.go (1)
  • SafeExtractStringSlice (432-460)
core/schemas/chatcompletions.go (2)
core/schemas/providers/gemini/types.go (2)
  • FileData (1073-1081)
  • Type (778-778)
ui/lib/types/logs.ts (2)
  • Citation (227-234)
  • Function (138-142)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (50)
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
  • GitHub Check: Graphite / mergeability_check
🔇 Additional comments (18)
core/schemas/providers/cohere/responses.go (5)

35-65: LGTM! Pointer-to-value slice refactor is correct.

The migration from pointer-based to value-based slices for stop sequences (line 35) and Tools (line 65) is implemented correctly. SafeExtractStringSlice handles both []string and []*string inputs safely, and the direct slice assignment on line 65 is protected by the length check on line 64.


173-190: LGTM! Content block iteration refactored correctly.

The direct iteration over msg.Content.ContentBlocks (line 173) and passing the slice directly to the conversion function (line 190) are both correct. The nil checks on lines 172 and 189 ensure these operations are safe.


228-257: LGTM! Tool call handling updated consistently.

The direct slice assignments for ToolCalls (line 228) and content block conversions (line 257) align with the pointer-to-value refactor. Both are protected by appropriate nil checks on lines 227 and 256.


333-353: LGTM! Output conversion logic correctly refactored.

The direct iterations over BlocksContent (line 333) and ToolCalls (line 353), along with the direct slice assignment (line 337), are all safe. The else-if branch on line 331 ensures BlocksContent is non-nil, and line 352 protects the ToolCalls iteration.


35-353: Slice field types validated
CohereChatRequest.Tools, CohereMessage.ToolCalls, and ResponsesMessageContent.ContentBlocks are all defined as slice types, so no further refactor is needed.

core/schemas/providers/anthropic/chat.go (5)

34-45: LGTM! Pointer-to-slice refactoring simplifies the code.

The change from iterating over *request.System.ContentBlocks to direct slice access (request.System.ContentBlocks) removes unnecessary pointer indirection. The nil check at line 32 remains appropriate for slices, and the direct assignment at line 45 (ContentBlocks: contentBlocks) is cleaner than the previous pointer-based approach.


65-126: LGTM! Content block and tool call conversions updated consistently.

The refactoring successfully removes pointer indirection for ContentBlocks (lines 65, 101, 120) and ToolCalls (line 126). The iteration patterns and assignments are correct, and the nil checks at lines 60, 100 remain valid for slices.


163-186: LGTM! Tools conversion simplified with direct slice access.

The change from iterating over a pointer (*request.Tools) to direct slice access (line 163) and the direct assignment at line 376 (Tools = tools) are correct and consistent with the overall refactoring.


423-528: LGTM! Message conversion logic handles direct slices correctly.

The refactoring updates multiple conversion paths:

  • System content blocks (lines 423-433)
  • Tool result content blocks (lines 451-463)
  • Regular message content blocks (lines 489-513)
  • Final content assignment (line 528)

All iterations and assignments follow the direct-slice pattern consistently. The nil checks remain appropriate, and the slice construction logic is correct.


582-594: LGTM! Response conversion updated to use direct slices.

The changes at lines 582 and 594 correctly iterate over ContentBlocks and ToolCalls as direct slices. The conversion logic remains semantically correct.

core/schemas/providers/anthropic/responses.go (8)

38-38: LGTM! Stop sequences assigned as direct slice value.

The change from pointer-to-slice to direct slice assignment (params.ExtraParams["stop"] = request.StopSequences) is correct and consistent with the refactoring pattern.


53-90: LGTM! System content and tools conversion simplified.

The direct iteration over request.System.ContentBlocks (line 53) and request.Tools (line 82) removes unnecessary pointer indirection. The conversion logic remains correct.


132-147: LGTM! Stop sequences and tools extraction updated correctly.

The usage of SafeExtractStringSlice at line 132 correctly returns a slice, and the direct assignment of anthropicTools at line 147 (Tools = anthropicTools) is consistent with the refactoring.


295-366: LGTM! Message conversion handles direct slices correctly.

The refactoring updates content block iterations and assignments throughout the conversion logic. The patterns are consistent and correct.


447-454: LGTM! Good use of defensive copying to avoid aliasing issues.

The pattern of explicitly copying slices before assignment (e.g., copied := make([]AnthropicContentBlock, len(pendingToolCalls)); copy(copied, pendingToolCalls)) prevents unintended mutations when pendingToolCalls is reused across iterations. This is good defensive programming practice.

Also applies to: 587-595, 727-734


466-513: LGTM! System and regular message content blocks handled correctly.

The direct iteration over content blocks (lines 466, 506) and direct slice assignments (lines 473, 513) are consistent with the refactoring. The nil checks remain appropriate for slices.


540-552: LGTM! Content block aliasing is safe in this context.

The aliasing of targetMsg.Content.ContentBlocks at line 540 is safe because:

  1. targetMsg is a valid pointer (either to existing element or newly appended element)
  2. The append at line 548 may reallocate, but the result is reassigned
  3. The entire Content struct is replaced at line 551, ensuring the new slice is used

This differs from the defensive copying used with pendingToolCalls because contentBlocks is built up once and immediately assigned, not reused across iterations.


621-635: LGTM! Tool output and content block conversions updated consistently.

The direct iteration over content blocks (lines 621, 894, 943) and direct slice assignments (lines 628, 635, 962) follow the refactoring pattern correctly. The conversion logic remains semantically correct.

Also applies to: 894-900, 943-962

@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 025f8a9 to 29fb578 Compare October 6, 2025 05:03
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from 29fb578 to feddb47 Compare October 6, 2025 05:05
@akshaydeo akshaydeo force-pushed the 09-15-seggreate_http_server_into_a_separate_struct branch from feddb47 to 6110981 Compare October 6, 2025 05:28
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