feat: add ConfigMarshallerPlugin interface and EnvVar support for OTEL and Prometheus plugin configs - #3651
Conversation
📝 WalkthroughWalkthroughAdds EnvVar-backed configuration across OTel and Prometheus: backend structs use EnvVar types with MarshalForStorage/Redacted, runtime init resolves EnvVar values, HTTP handlers normalize/expand configs for storage/API, UI schemas accept env refs, and form components use EnvVar-aware inputs and defaults. ChangesEnvironment Variable Support for Observability Plugins
Estimated Code Review Effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related PRs
Suggested Reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
This stack of pull requests is managed by Graphite. Learn more about stacking. |
EnvVar support for plugin config fields with MarshalForStorage and Redacted helpers for otel and telemetry plugins
Confidence Score: 4/5Safe to merge with awareness of two open items from earlier review rounds that remain in the codebase. The round-trip credential corruption concern from the previous review is addressed by restoreRedactedFromExisting and by hideResolvedEnvValue correctly leaving literal URLs unmasked. Two previously flagged issues remain open: ValidateConfig in plugins/otel still uses GetValue() and resolveHeaders still silently produces empty header entries for unresolved env-var references. plugins/otel/main.go — ValidateConfig and resolveHeaders retain the GetValue()-vs-IsSet() gap; plugins/telemetry/main.go — EnablePushGateway URL guard still uses GetValue(). Important Files Changed
Reviews (13): Last reviewed commit: "feat: add support for env var in otel an..." | Re-trigger Greptile |
8edc339 to
f36f9a7
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
ui/app/workspace/observability/fragments/otelFormFragment.tsx (1)
159-167:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd
data-testidto the new EnvVar inputs for E2E stability.The new OTEL collector and metrics endpoint inputs are interactive but missing selectors, which makes E2E targeting brittle.
💡 Suggested fix
<EnvVarInput + data-testid="otel-collector-url-input" placeholder={...} disabled={!hasOtelAccess} {...field} /> <EnvVarInput + data-testid="otel-metrics-endpoint-input" placeholder={...} disabled={!hasOtelAccess} {...field} />As per coding guidelines, "Add data-testid to all new interactive elements in React components for E2E test compatibility."
Also applies to: 340-348
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx` around lines 159 - 167, The EnvVarInput components representing the OTEL collector and metrics endpoint inputs are missing data-testid attributes which E2E tests rely on; update the EnvVarInput instances in otelFormFragment (the inputs that use form.watch("otel_config.protocol") for placeholder and the other OTEL collector/metrics endpoint EnvVarInput) to add unique data-testid values (e.g., data-testid="otel-collector-input" and data-testid="otel-metrics-endpoint-input"), preserving existing props like disabled={!hasOtelAccess} and {...field} so selectors are stable for tests.plugins/otel/main.go (1)
265-276:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate resolved OTEL endpoints, not just EnvVar presence.
IsSet()will still accept afrom_envconfig whose environment variable is missing, socollector_url/metrics_endpointcan pass validation and then resolve to""when the client or metrics exporter is created. These required fields should be checked with the resolved value instead.Suggested fix
- if !config.MetricsEndpoint.IsSet() { + if config.MetricsEndpoint.GetValue() == "" { return nil, fmt.Errorf("metrics_endpoint is required when metrics_enabled is true") }- if !otelConfig.CollectorURL.IsSet() { + if otelConfig.CollectorURL.GetValue() == "" { return nil, fmt.Errorf("collector url is required") }Based on learnings, to check whether an EnvVar config is both present and resolved, use
cfg.Field != nil && cfg.Field.GetValue() != "", andGetValue()is nil-receiver safe in this repo.Also applies to: 341-342
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/otel/main.go` around lines 265 - 276, The validation currently uses config.MetricsEndpoint.IsSet() which can be true even if the env var resolves to an empty string; change the check to validate the resolved value (e.g., ensure config.MetricsEndpoint != nil && config.MetricsEndpoint.GetValue() != "") before returning an error, and use the same pattern for collector_url/collector endpoint checks mentioned (apply to the other checks at lines ~341-342); keep using MetricsConfig.ServiceName and MetricsConfig.Endpoint with config.MetricsEndpoint.GetValue() after you verify it is non-nil and non-empty, and retain the existing pushInterval bounds logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@plugins/telemetry/main.go`:
- Around line 471-472: The PushGateway startup currently gates on
PushGatewayURL.IsSet(), which can be true for env-var fields that resolve to
empty strings; change the check to require the resolved value by using
PushGatewayURL != nil && PushGatewayURL.GetValue() != "" before calling
plugin.EnablePushGateway, and inside EnablePushGateway ensure BasicAuth is only
used when both username and password are non-empty via their GetValue() checks
(e.g., check Username != nil && Username.GetValue() != "" and Password != nil &&
Password.GetValue() != ""), updating the same pattern in the other occurrences
referenced in the review (the PushGateway URL and auth handling code paths).
In `@transports/bifrost-http/handlers/plugins.go`:
- Around line 61-98: normalizePluginConfig is only used in updatePlugin so
createPlugin still writes request.Config directly and persists full EnvVar
objects; fix by invoking normalizePluginConfig(name, request.Config) (or the
equivalent) before any DB write/reload in createPlugin so the plugin config
passes through MarshalForStorage (same for the update path where request.Config
is written). Ensure you call normalizePluginConfig for plugin names that match
otel.PluginName and telemetry.PluginName (i.e. when creating/updating
otel.Config or telemetry.Config) and use the returned map for the DB
insert/update and reload steps instead of the original request.Config.
- Around line 104-141: The function expandPluginConfigForAPI currently returns
the original unredacted config on any error, which can leak secrets; modify
expandPluginConfigForAPI (including the toMap helper and the OTEL/telemetry
switch branches) to "fail closed" by returning a safe redacted representation
instead of the raw config when any marshal/unmarshal/Redacted call fails—e.g.,
return nil or a small sanitized map indicating redaction—so that whenever
sonic.Marshal/Unmarshal or c.Redacted() fails the function does not echo secrets
back to clients.
In `@ui/lib/types/schemas.ts`:
- Around line 824-825: The current presence checks treat a field as provided
when its .from_env flag is true even if the corresponding .env_var is
empty/whitespace; update the logic for metrics_endpoint (and the two other
similar blocks) to treat an env-ref as present only when from_env is true AND
the trimmed env_var is non-empty: replace uses of
data.metrics_endpoint?.from_env (and the equivalents) with a guard that checks
data.metrics_endpoint?.from_env &&
(data.metrics_endpoint?.env_var?.trim()?.length > 0), and use that compound
boolean wherever metricsIsEnvRef (and the analogous variables for the other
fields) are computed so required/pair validation fails when env_var is blank.
---
Outside diff comments:
In `@plugins/otel/main.go`:
- Around line 265-276: The validation currently uses
config.MetricsEndpoint.IsSet() which can be true even if the env var resolves to
an empty string; change the check to validate the resolved value (e.g., ensure
config.MetricsEndpoint != nil && config.MetricsEndpoint.GetValue() != "") before
returning an error, and use the same pattern for collector_url/collector
endpoint checks mentioned (apply to the other checks at lines ~341-342); keep
using MetricsConfig.ServiceName and MetricsConfig.Endpoint with
config.MetricsEndpoint.GetValue() after you verify it is non-nil and non-empty,
and retain the existing pushInterval bounds logic.
In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx`:
- Around line 159-167: The EnvVarInput components representing the OTEL
collector and metrics endpoint inputs are missing data-testid attributes which
E2E tests rely on; update the EnvVarInput instances in otelFormFragment (the
inputs that use form.watch("otel_config.protocol") for placeholder and the other
OTEL collector/metrics endpoint EnvVarInput) to add unique data-testid values
(e.g., data-testid="otel-collector-input" and
data-testid="otel-metrics-endpoint-input"), preserving existing props like
disabled={!hasOtelAccess} and {...field} so selectors are stable for tests.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 84eeecc6-dd51-419f-8787-3567e880ac6c
📒 Files selected for processing (8)
plugins/otel/main.goplugins/telemetry/main.gotransports/bifrost-http/handlers/plugins.goui/app/workspace/observability/fragments/otelFormFragment.tsxui/app/workspace/observability/fragments/prometheusFormFragment.tsxui/app/workspace/observability/views/plugins/prometheusView.tsxui/lib/types/schemas.tsui/lib/utils/envVarForm.ts
f36f9a7 to
2260182
Compare
There was a problem hiding this comment.
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (2)
ui/app/workspace/observability/fragments/otelFormFragment.tsx (1)
141-149:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd stable
data-testidvalues to newEnvVarInputcontrols.Both newly introduced interactive fields (
collector_url,metrics_endpoint) should expose explicitdata-testidattributes for E2E selectors, consistent with the rest of the form.Suggested update
<EnvVarInput + data-testid="otel-collector-url-input" placeholder={ form.watch("otel_config.protocol") === "http" ? "https://otel-collector.example.com:4318/v1/traces or env.OTEL_COLLECTOR_URL" : "otel-collector.example.com:4317 or env.OTEL_COLLECTOR_URL" } disabled={!hasOtelAccess} {...field} /><EnvVarInput + data-testid="otel-metrics-endpoint-input" placeholder={ form.watch("otel_config.protocol") === "http" ? "https://otel-collector:4318/v1/metrics or env.OTEL_METRICS_ENDPOINT" : "otel-collector:4317 or env.OTEL_METRICS_ENDPOINT" } disabled={!hasOtelAccess} {...field} />As per coding guidelines: “Add data-testid to all new interactive elements in React components for E2E test compatibility.”
Also applies to: 322-330
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx` around lines 141 - 149, The new EnvVarInput controls for the OTEL form (the fields tied to collector_url and metrics_endpoint) lack stable data-testid attributes needed for E2E tests; update the EnvVarInput instances in otelFormFragment.tsx (the instances using form.watch("otel_config.protocol") and the other metrics_endpoint input around lines referenced) to include explicit data-testid props (e.g., data-testid="otel-collector-url" and data-testid="otel-metrics-endpoint") while preserving the existing disabled handling (hasOtelAccess) and spread {...field} so selectors can reliably target these interactive elements.plugins/otel/main.go (1)
236-255:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate required OTEL endpoints after env resolution.
IsSet()still letsenv.MISSING_VARpass here, so this code can accept the config and then initialize the OTEL client / metrics exporter with""as the endpoint.Suggested fix
func Init(ctx context.Context, config *Config, _logger schemas.Logger, pricingManager *modelcatalog.ModelCatalog, bifrostVersion string) (*OtelPlugin, error) { if config == nil { return nil, fmt.Errorf("config is required") } + collectorURL := config.CollectorURL.GetValue() + if collectorURL == "" { + return nil, fmt.Errorf("collector_url is required") + } logger = _logger @@ p := &OtelPlugin{ serviceName: config.ServiceName, - url: config.CollectorURL.GetValue(), + url: collectorURL, @@ if config.Protocol == ProtocolGRPC { - p.client, err = NewOtelClientGRPC(config.CollectorURL.GetValue(), p.headers, config.TLSCACert, config.Insecure) + p.client, err = NewOtelClientGRPC(collectorURL, p.headers, config.TLSCACert, config.Insecure) @@ if config.Protocol == ProtocolHTTP { - p.client, err = NewOtelClientHTTP(config.CollectorURL.GetValue(), p.headers, config.TLSCACert, config.Insecure) + p.client, err = NewOtelClientHTTP(collectorURL, p.headers, config.TLSCACert, config.Insecure) @@ if config.MetricsEnabled { - if !config.MetricsEndpoint.IsSet() { + metricsEndpoint := config.MetricsEndpoint.GetValue() + if metricsEndpoint == "" { return nil, fmt.Errorf("metrics_endpoint is required when metrics_enabled is true") } @@ metricsConfig := &MetricsConfig{ ServiceName: config.ServiceName, - Endpoint: config.MetricsEndpoint.GetValue(), + Endpoint: metricsEndpoint, @@ - if !otelConfig.CollectorURL.IsSet() { + if otelConfig.CollectorURL.GetValue() == "" { return nil, fmt.Errorf("collector url is required") }Based on learnings, for
*schemas.EnvVarfields in this repo the runtime “configured and resolved” check isfield != nil && field.GetValue() != "".Also applies to: 265-276, 341-342
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/otel/main.go` around lines 236 - 255, The OTEL endpoint validation is insufficient because config.CollectorURL.IsSet() can pass with an unresolved env var; update the initialization guards to require the EnvVar to be non-nil and have a non-empty value (i.e., config.CollectorURL != nil && config.CollectorURL.GetValue() != "") before calling NewOtelClientGRPC, NewOtelClientHTTP or initializing any metrics exporter; if the check fails return a clear error indicating the missing endpoint. Apply the same nil+GetValue() != "" validation wherever EnvVar fields are used to drive ProtocolGRPC/ProtocolHTTP client creation and exporter setup (references: config.CollectorURL, ProtocolGRPC, ProtocolHTTP, NewOtelClientGRPC, NewOtelClientHTTP, and the metrics exporter initialization sites).
♻️ Duplicate comments (1)
ui/lib/types/schemas.ts (1)
823-825:⚠️ Potential issue | 🟠 Major | ⚡ Quick winTrim
env_varbefore treating an env-ref as present.These checks still accept
{ from_env: true, env_var: " " }as “set”, so OTEL configs can pass required validation with an unusable env reference.Suggested fix
+const isEnvRefSet = (v?: { from_env?: boolean; env_var?: string }) => + !!v?.from_env && !!v?.env_var?.trim(); + // ... -const metricsIsEnvRef = data.metrics_endpoint?.from_env && data.metrics_endpoint?.env_var; +const metricsIsEnvRef = isEnvRefSet(data.metrics_endpoint); // ... -const isEnvRef = data.otel_config.collector_url?.from_env && data.otel_config.collector_url?.env_var; +const isEnvRef = isEnvRefSet(data.otel_config.collector_url);Also applies to: 847-849
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/lib/types/schemas.ts` around lines 823 - 825, The code treats an env-ref as present when data.metrics_endpoint?.env_var contains only whitespace; change the check to trim env_var before testing for non-empty. Concretely, compute a trimmed env var string (e.g., metricsEnvVar = (data.metrics_endpoint?.env_var || "").trim()) and use data.metrics_endpoint?.from_env && metricsEnvVar !== "" for metricsIsEnvRef, and apply the same trimmed-env check to the analogous logs_endpoint/env-ref logic so whitespace-only env_var values are rejected.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Outside diff comments:
In `@plugins/otel/main.go`:
- Around line 236-255: The OTEL endpoint validation is insufficient because
config.CollectorURL.IsSet() can pass with an unresolved env var; update the
initialization guards to require the EnvVar to be non-nil and have a non-empty
value (i.e., config.CollectorURL != nil && config.CollectorURL.GetValue() != "")
before calling NewOtelClientGRPC, NewOtelClientHTTP or initializing any metrics
exporter; if the check fails return a clear error indicating the missing
endpoint. Apply the same nil+GetValue() != "" validation wherever EnvVar fields
are used to drive ProtocolGRPC/ProtocolHTTP client creation and exporter setup
(references: config.CollectorURL, ProtocolGRPC, ProtocolHTTP, NewOtelClientGRPC,
NewOtelClientHTTP, and the metrics exporter initialization sites).
In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx`:
- Around line 141-149: The new EnvVarInput controls for the OTEL form (the
fields tied to collector_url and metrics_endpoint) lack stable data-testid
attributes needed for E2E tests; update the EnvVarInput instances in
otelFormFragment.tsx (the instances using form.watch("otel_config.protocol") and
the other metrics_endpoint input around lines referenced) to include explicit
data-testid props (e.g., data-testid="otel-collector-url" and
data-testid="otel-metrics-endpoint") while preserving the existing disabled
handling (hasOtelAccess) and spread {...field} so selectors can reliably target
these interactive elements.
---
Duplicate comments:
In `@ui/lib/types/schemas.ts`:
- Around line 823-825: The code treats an env-ref as present when
data.metrics_endpoint?.env_var contains only whitespace; change the check to
trim env_var before testing for non-empty. Concretely, compute a trimmed env var
string (e.g., metricsEnvVar = (data.metrics_endpoint?.env_var || "").trim()) and
use data.metrics_endpoint?.from_env && metricsEnvVar !== "" for metricsIsEnvRef,
and apply the same trimmed-env check to the analogous logs_endpoint/env-ref
logic so whitespace-only env_var values are rejected.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 12da6902-a29f-48d5-be41-27b1f9fa4e33
📒 Files selected for processing (8)
plugins/otel/main.goplugins/telemetry/main.gotransports/bifrost-http/handlers/plugins.goui/app/workspace/observability/fragments/otelFormFragment.tsxui/app/workspace/observability/fragments/prometheusFormFragment.tsxui/app/workspace/observability/views/plugins/prometheusView.tsxui/lib/types/schemas.tsui/lib/utils/envVarForm.ts
2260182 to
7784c64
Compare
EnvVar support for plugin config fields with MarshalForStorage and Redacted helpers for otel and telemetry pluginsEnvVar support for sensitive fields in otel and telemetry plugin configs with storage normalization and API redaction
There was a problem hiding this comment.
Actionable comments posted: 2
♻️ Duplicate comments (1)
plugins/otel/main.go (1)
236-255:⚠️ Potential issue | 🟠 Major | ⚡ Quick winValidate resolved OTEL endpoints, not just EnvVar presence.
IsSet()still passes configs likeenv.MISSING_VAR, socollector_urlandmetrics_endpointcan get through validation and then resolve to""whenNewOtelClient*or the metrics exporter is created. That turns a missing env var into a startup/runtime failure instead of a config error.🛠️ Suggested fix
- if config.MetricsEnabled { - if !config.MetricsEndpoint.IsSet() { + if config.MetricsEnabled { + if config.MetricsEndpoint.GetValue() == "" { return nil, fmt.Errorf("metrics_endpoint is required when metrics_enabled is true") }- if !otelConfig.CollectorURL.IsSet() { + if otelConfig.CollectorURL.GetValue() == "" { return nil, fmt.Errorf("collector url is required") }Based on learnings, to check whether an EnvVar config is both present and resolved, use
cfg.Field != nil && cfg.Field.GetValue() != "", andGetValue()is nil-receiver safe in this repo.Also applies to: 265-266, 341-342
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/otel/main.go` around lines 236 - 255, The code validates presence of EnvVar-backed configs using IsSet() but then passes empty resolved values into NewOtelClientGRPC/NewOtelClientHTTP (and metrics exporter creation), causing runtime failures; change the checks to ensure the config fields are both non-nil and resolve to non-empty strings (e.g., config.CollectorURL != nil && config.CollectorURL.GetValue() != "") before constructing the OTEL clients or metrics exporter, and return a clear config error if the resolved value is empty; update the branches that call NewOtelClientGRPC, NewOtelClientHTTP and the metrics exporter creation to use this resolved-value check (reference symbols: config.CollectorURL, config.MetricsEndpoint, NewOtelClientGRPC, NewOtelClientHTTP, and the metrics exporter creation code paths).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@ui/app/workspace/observability/fragments/prometheusFormFragment.tsx`:
- Around line 39-40: The hasAuth function treats whitespace-only plain strings
as truthy; update it to trim plain strings before checking so whitespace doesn't
count as configured auth (in hasAuth, when typeof v === "string" use v.trim()
for the truthiness check). For the EnvVar branch, keep the existing checks on
v.value.trim() and v.from_env && v.env_var.trim() (ensure you call trim() safely
on those fields) so only non-empty trimmed values or env-based vars return true;
reference function hasAuth and EnvVar fields value, from_env, and env_var when
making the change.
In `@ui/lib/types/schemas.ts`:
- Around line 824-825: Validation currently calls
isEnvVarSet(data.metrics_endpoint) (and similar checks) without ensuring the
corresponding from_env flag is true, allowing env_var values to pass validation
even though payload serialization ignores env refs unless from_env === true;
update the validation logic for the affected fields (e.g., metrics_endpoint,
other endpoints at the noted spots and any checks around isEnvVarSet) to require
both from_env === true and isEnvVarSet(env_var) (or alternatively change
isEnvVarSet to accept the whole field object and return true only when from_env
=== true && env_var is set), then adjust the ctx.addIssue branches that
reference isEnvVarSet to use this combined check so required/pair validation
matches serialization semantics.
---
Duplicate comments:
In `@plugins/otel/main.go`:
- Around line 236-255: The code validates presence of EnvVar-backed configs
using IsSet() but then passes empty resolved values into
NewOtelClientGRPC/NewOtelClientHTTP (and metrics exporter creation), causing
runtime failures; change the checks to ensure the config fields are both non-nil
and resolve to non-empty strings (e.g., config.CollectorURL != nil &&
config.CollectorURL.GetValue() != "") before constructing the OTEL clients or
metrics exporter, and return a clear config error if the resolved value is
empty; update the branches that call NewOtelClientGRPC, NewOtelClientHTTP and
the metrics exporter creation to use this resolved-value check (reference
symbols: config.CollectorURL, config.MetricsEndpoint, NewOtelClientGRPC,
NewOtelClientHTTP, and the metrics exporter creation code paths).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 816a69db-67d4-41ea-a0da-528867b1cf0a
📒 Files selected for processing (8)
plugins/otel/main.goplugins/telemetry/main.gotransports/bifrost-http/handlers/plugins.goui/app/workspace/observability/fragments/otelFormFragment.tsxui/app/workspace/observability/fragments/prometheusFormFragment.tsxui/app/workspace/observability/views/plugins/prometheusView.tsxui/lib/types/schemas.tsui/lib/utils/envVarForm.ts
f59c88c to
ff463d9
Compare
7784c64 to
7e893ce
Compare
The merge-base changed after approval.
3226eb6 to
ed1ecc3
Compare
There was a problem hiding this comment.
Actionable comments posted: 3
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
ui/app/workspace/observability/fragments/otelFormFragment.tsx (1)
141-149:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winAdd stable test ids to the new EnvVar inputs.
These are new interactive controls, but they still don't expose stable selectors for E2E coverage of the env-var flow.
As per coding guidelines, "Add data-testid to all new interactive elements in React components for E2E test compatibility" and the convention is `data-testid="--"`.Suggested change
<EnvVarInput + data-testid="otel-collector-url-input" placeholder={ form.watch("otel_config.protocol") === "http" ? "https://otel-collector.example.com:4318/v1/traces or env.OTEL_COLLECTOR_URL" : "otel-collector.example.com:4317 or env.OTEL_COLLECTOR_URL" } disabled={!hasOtelAccess} {...field} /><EnvVarInput + data-testid="otel-metrics-endpoint-input" placeholder={ form.watch("otel_config.protocol") === "http" ? "https://otel-collector:4318/v1/metrics or env.OTEL_METRICS_ENDPOINT" : "otel-collector:4317 or env.OTEL_METRICS_ENDPOINT" } disabled={!hasOtelAccess} {...field} />Also applies to: 322-330
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx` around lines 141 - 149, The new EnvVarInput controls (see EnvVarInput usage with placeholder based on form.watch("otel_config.protocol") and disabled controlled by hasOtelAccess) lack stable selectors for E2E tests; add data-testid attributes following the convention data-testid="otel-collector-env-var-input" (and for the similar inputs referenced around the other block) to each interactive element so tests can reliably select them—ensure each EnvVarInput instance gets a unique, descriptive test id (e.g., otel-collector-env-var-input or otel-collector-<qualifier>-env-var-input) matching the project naming scheme.
♻️ Duplicate comments (1)
plugins/telemetry/main.go (1)
892-893:⚠️ Potential issue | 🟠 Major | ⚡ Quick winRequire resolved Basic Auth values before attaching credentials.
IsSet()is still true for env-backed fields whose referenced variables resolve to"", so this path can send empty Basic Auth credentials and break Push Gateway auth when the env vars are missing at runtime.Suggested fix
- if config.BasicAuth != nil && config.BasicAuth.Username.IsSet() && config.BasicAuth.Password.IsSet() { + if config.BasicAuth != nil && + config.BasicAuth.Username.GetValue() != "" && + config.BasicAuth.Password.GetValue() != "" { pusher = pusher.BasicAuth(config.BasicAuth.Username.GetValue(), config.BasicAuth.Password.GetValue()) }Based on learnings, to check whether an EnvVar config is both present and resolved, use
cfg.Field != nil && cfg.Field.GetValue() != "", andGetValue()is nil-receiver safe in this repo.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@plugins/telemetry/main.go` around lines 892 - 893, The current check uses Username.IsSet() and Password.IsSet(), which can be true even when env-backed fields resolve to empty strings; update the conditional that sets pusher (the block using config.BasicAuth and pusher = pusher.BasicAuth(...)) to require resolved non-empty values by checking config.BasicAuth != nil && config.BasicAuth.Username.GetValue() != "" && config.BasicAuth.Password.GetValue() != "" (GetValue() is nil-receiver safe in this repo) before calling pusher.BasicAuth so empty credentials are never sent.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@transports/bifrost-http/handlers/plugins_test.go`:
- Around line 54-59: The noopPluginsLoader methods NormalizePluginConfig and
ExpandPluginConfigForAPI currently return nil which mutates behavior; update
both methods to return the incoming config map unchanged (passthrough) along
with nil error so callers receive the original config; locate the
noopPluginsLoader type and modify NormalizePluginConfig and
ExpandPluginConfigForAPI to return the provided map[string]any parameter instead
of nil.
In `@transports/bifrost-http/handlers/plugins.go`:
- Around line 66-92: The wrappers normalizePluginConfig and
expandPluginConfigForAPI must not return the raw config when the plugin isn't
loaded; change both so that if pluginsLoader.NormalizePluginConfig or
ExpandPluginConfigForAPI returns out == nil, the wrapper returns (nil, nil)
instead of returning the original config. Update normalizePluginConfig and
expandPluginConfigForAPI to propagate a nil result from pluginsLoader (so
callers can detect an unloaded plugin) rather than falling back to the
unredacted/unstored config.
- Around line 132-136: The current loop compares plugin.Name to status.Name
(display name) which breaks when display names diverge; instead retrieve the
status by map key using pluginStatuses[plugin.Name] (or check the map key while
iterating) and assign that to pluginStatus so the actual plugin key, not the
display name, determines the returned state; update the code around the
pluginStatuses lookup in handlers/plugins.go to use the map key lookup and
preserve the renamed display name in the response.
---
Outside diff comments:
In `@ui/app/workspace/observability/fragments/otelFormFragment.tsx`:
- Around line 141-149: The new EnvVarInput controls (see EnvVarInput usage with
placeholder based on form.watch("otel_config.protocol") and disabled controlled
by hasOtelAccess) lack stable selectors for E2E tests; add data-testid
attributes following the convention data-testid="otel-collector-env-var-input"
(and for the similar inputs referenced around the other block) to each
interactive element so tests can reliably select them—ensure each EnvVarInput
instance gets a unique, descriptive test id (e.g., otel-collector-env-var-input
or otel-collector-<qualifier>-env-var-input) matching the project naming scheme.
---
Duplicate comments:
In `@plugins/telemetry/main.go`:
- Around line 892-893: The current check uses Username.IsSet() and
Password.IsSet(), which can be true even when env-backed fields resolve to empty
strings; update the conditional that sets pusher (the block using
config.BasicAuth and pusher = pusher.BasicAuth(...)) to require resolved
non-empty values by checking config.BasicAuth != nil &&
config.BasicAuth.Username.GetValue() != "" &&
config.BasicAuth.Password.GetValue() != "" (GetValue() is nil-receiver safe in
this repo) before calling pusher.BasicAuth so empty credentials are never sent.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 02b3dc5d-329e-4b9f-97d4-ba6ae5c34b8b
📒 Files selected for processing (13)
core/schemas/plugin.goplugins/otel/main.goplugins/telemetry/go.modplugins/telemetry/main.gotransports/bifrost-http/handlers/plugins.gotransports/bifrost-http/handlers/plugins_test.gotransports/bifrost-http/lib/config.gotransports/bifrost-http/server/server.goui/app/workspace/observability/fragments/otelFormFragment.tsxui/app/workspace/observability/fragments/prometheusFormFragment.tsxui/app/workspace/observability/views/plugins/prometheusView.tsxui/lib/types/schemas.tsui/lib/utils/envVarForm.ts
✅ Files skipped from review due to trivial changes (1)
- plugins/telemetry/go.mod
ed1ecc3 to
597f576
Compare
597f576 to
cd0331b
Compare
ConfigMarshallerPlugin interface with EnvVar support for otel and prometheus plugin configsConfigMarshallerPlugin interface and EnvVar support for OTEL and Prometheus plugin configs
Merge activity
|
… OTEL and Prometheus plugin configs (#3651) ## Summary This PR introduces `EnvVar`-typed fields for sensitive and configurable URL/credential values in the OpenTelemetry and Prometheus (telemetry) plugins, replacing raw `string` fields. This allows users to reference environment variables (e.g., `env.OTEL_COLLECTOR_URL`) instead of embedding literal values in stored configuration, improving secret management and deployment flexibility. ## Changes - **`plugins/otel`**: `CollectorURL`, `MetricsEndpoint`, and `Headers` values in `Config` are now `*schemas.EnvVar` instead of `string`/`map[string]string`. Added `MarshalForStorage()` to serialize back to plain strings for DB persistence, `Redacted()` for safe API responses, and `resolveHeaders()` to convert `EnvVar` header maps to plain strings at runtime. Removed the inline `env.` prefix resolution loop from `Init` in favor of `EnvVar.GetValue()`. - **`plugins/telemetry`**: `PushGatewayURL`, `BasicAuth.Username`, and `BasicAuth.Password` in `PushGatewayConfig`/`BasicAuthConfig` are now `*schemas.EnvVar`. Added `MarshalForStorage()` and `Redacted()` to `Config` with the same storage/API separation pattern. - **`core/schemas`**: Introduced the `ConfigMarshallerPlugin` interface, optionally implemented by plugins that need custom config serialization. The server calls `MarshalConfigForStorage` before writing config to the DB and `RedactConfig` when building API responses. Both the OTEL and telemetry plugins implement this interface. - **`transports/bifrost-http/handlers/plugins.go`**: Added `normalizePluginConfig()` to round-trip plugin configs through their typed structs before DB writes (ensuring `EnvVar` → plain string serialization), and `expandPluginConfigForAPI()` to expand stored plain strings back into full `EnvVar` objects with redaction for API responses. Refactored `getPlugins` to use `buildPluginResponseWithStatuses` to avoid redundant status fetches per plugin. - **`transports/bifrost-http/lib/config.go`**: Added a `ConfigMarshallers` atomic cache derived from `BasePlugins`, rebuilt alongside the other interface caches on any plugin change. - **`transports/bifrost-http/server/server.go`**: Implemented `NormalizePluginConfig` and `ExpandPluginConfigForAPI` on `BifrostHTTPServer`, backed by the `ConfigMarshallers` cache. - **UI schemas (`ui/lib/types/schemas.ts`)**: Updated `otelConfigSchema` and `prometheusConfigSchema` to use `envVarSchema` for URL and credential fields. Validation logic now skips format checks for env var references (`from_env: true`) and checks `value` or `from_env` presence instead of raw string truthiness. - **UI forms**: Replaced plain `<Input>` components with `<EnvVarInput>` for collector URL, metrics endpoint, push gateway URL, and basic auth fields in the OTEL and Prometheus form fragments. The password field now uses `EnvVarInput` with `hideValueWhenEnv` and `redactNonEnvValue` props, removing the manual show/hide toggle. `HeadersTable` now uses `useEnvVarInput` mode. - **`ui/lib/utils/envVarForm.ts`**: Fixed `toEnvVarFormValue` to clear `value` when the input is an env reference string. Added `toEnvVarMapFormValue` to convert header maps of mixed `string | EnvVar` values into typed `EnvVar` form values. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... ./plugins/telemetry/... ./transports/bifrost-http/... # UI cd ui pnpm i pnpm build pnpm test ``` **Manual validation:** 1. Configure the OTEL plugin with `collector_url` set to `env.OTEL_COLLECTOR_URL` and verify the environment variable is resolved at runtime. 2. Set a literal URL and confirm it is stored and returned correctly. 3. Configure Prometheus push gateway with `env.PUSHGATEWAY_URL` and basic auth credentials via env vars; verify metrics are pushed correctly. 4. Confirm API responses show full `EnvVar` objects with sensitive values redacted. 5. Confirm DB-stored configs contain plain strings (`env.FOO` or literal values), not JSON objects. ## Breaking changes - [x] Yes - [ ] No The `Config` structs for the OTEL and telemetry plugins have changed field types from `string` to `*schemas.EnvVar`. Any code directly constructing these structs (e.g., in tests or custom integrations) must be updated to wrap values using `schemas.NewEnvVar(...)` or equivalent. Configs already stored in the database as plain strings will be transparently upgraded on read via `EnvVar.UnmarshalJSON`. ## Security considerations Sensitive fields (collector URLs, push gateway URLs, basic auth credentials, and OTEL headers) are now redacted in API responses via the `Redacted()` methods. Credentials are never stored as resolved values — only as `env.VAR_NAME` references or literal strings as provided by the user. The `FullyRedacted()` method is applied to passwords specifically. ## 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
## ✨ Features
- **Azure v1 API Migration** — Migrated Azure provider to the v1 API:
removed the `api-version` query parameter and the
`/openai/deployments/{model}/...` URL pattern in favor of
`/openai/v1/{operation}`; the `api_version` field has been dropped from
`AzureKeyConfig` (#3661, #3756)
- **EnvVar Support for OTEL & Prometheus Configs** — `CollectorURL`,
`MetricsEndpoint`, headers, push gateway URL, and basic auth credentials
can now be sourced from environment variables (e.g.,
`env.OTEL_COLLECTOR_URL`); added a new `ConfigMarshallerPlugin`
interface that lets plugins control storage/redaction round-trips
(#3651)
- **OTel Extra Header Forwarding** — `x-bf-eh-*` extra headers forwarded
to upstream providers are now also emitted on the request span under
`gen_ai.request.extra_header.*` for end-to-end tracing (#3730)
- **OTel Semantic Conventions** — Aligned OTel attribute keys with the
OpenTelemetry GenAI spec (canonical `gen_ai.*` and new `bifrost.*`
attributes); legacy attributes are retained in parallel to avoid
breaking existing dashboards (#3732)
- **VK Quota with Provider Configs** — `GetVirtualKeyQuotaByValue` and
the `getVirtualKeyQuota` HTTP response now include `provider_configs`
with their budgets and rate limits (#3721)
- **MCP Temp Token Non-Auth Toggle** — Added
`mcp_enable_temp_token_auth` client config flag to gate short-lived MCP
token minting for non-authenticated users (#3720)
- **Responses Stream in JSON Parser** — `jsonparser` plugin now handles
OpenAI Responses API streaming (`ResponsesStreamRequest`) in addition to
chat completions (#3749)
- **Session API Rework** — Logout now calls both the password-based
session logout and OAuth logout endpoints and resets all RTK Query cache
state (#3698)
## 🐞 Fixed
- **Streaming Latency for Observability** — Deferred root span
termination to the trace completer callback for streaming requests so
request latency is no longer inflated by header-flush time (#3762)
- **Stream Cancellation Race** — Set `BifrostContextKeyConnectionClosed`
before closing the stream and short-circuit `idleTimeoutReader.Read`
when the connection is already closed to avoid panics and hangs on
cancellation (#3733)
- **Bedrock Cache Points** — Strip cache points from Bedrock requests
for models that do not support prompt caching (e.g., GLM, Llama) to
avoid Converse API errors (#3754)
- **Bedrock Empty Text Blocks** — Skip empty/nil text blocks during
Bedrock response conversion to avoid invalid messages (#3747)
- **Bedrock Reasoning + Tools** — Preserve reasoning content blocks on
assistant turns that also contain tool calls in the Bedrock chat
converter (#3690)
- **Bedrock Search Content & Video** — Restored search content and video
parts that were being dropped from Bedrock-native passthrough requests
(#3729)
- **Structured Output Stop Reason** — Fixed an incorrect `tool_calls`
finish reason when structured output is combined with extended-thinking
tools (#3685)
- **Gemini Tool Schema Passthrough** — Forward full tool parameter
schemas via `parametersJsonSchema` instead of the lossy `parameters`
form; corrected tool response role to `user`; resolved structured output
+ tools conflict (#3761)
- **Anthropic Stop Reason & Tool Versions** — Normalized stop reason
mapping (`end_turn` to `stop`, `tool_use` to `tool_calls`, `max_tokens`
to `length`) and upgraded `text_editor_20250124`/`str_replace_editor` to
`text_editor_20250728` for computer-use tools (#3761)
- **Azure Endpoint Redaction** — Fixed a panic when
`AzureKeyConfig.Endpoint` is a literal value rather than an env
reference (#3761)
- **Auth Middleware Path Match** — Match temp-token auth middleware
whitelist against the request path only, not the full URI with query
parameters (#3737)
- **Governance Blocked Models UI** — Restored the missing Blocked Models
create/edit UI in the VK provider config sheet (#3750)
- **Logging Plugin Cleanup Drain** — Fixed a shutdown race where
`batchWriter` could drop in-flight log entries; `Cleanup` now drains
both the recovered batch and remaining queue within a 30-second budget
(#3717)
- **Model Rankings Empty Entries** — Excluded entries with empty `model`
values from model rankings matview queries so blank rows no longer
surface in the UI (#3758)
- **User Filter Duplicates** — Recreated `mv_filter_users` matview to
require non-empty `user_name`, eliminating duplicate filter dropdown
entries (#3764)
- **User Filter Display Name** — Use `user_name` instead of `user_id` as
the display label for users in logging filters (#3691)
- **Large Numeric ID Precision** — Preserve large numeric IDs in URL
search params by skipping JSON parsing for plain strings (#3692)
## 🔧 Refactors & Chores
- **Error Propagation for GetAvailable\* APIs** — `GetAvailable*`
methods on `LoggerPlugin`/`LogManager` now return wrapped errors instead
of silently logging and returning empty slices (#3759)
- **Governance Blocklist Matching** — Use `slices.Contains` for VK
blocked-model matching for clearer code with identical semantics (#3727)
- **Exported `ResolvePeriod`** — Renamed `resolvePeriod` to
`ResolvePeriod` so external packages can reuse the period parsing
(#3763)
## 📚 Docs
- **OTEL Env Var Documentation** — Documented `env.VAR_NAME` support for
`collector_url`, `metrics_endpoint`, and headers in OTEL/Prometheus
plugin docs
- **OTEL OSS Features & Examples** — Added OTEL documentation to the OSS
features list with usage examples (#3731)
- **Anthropic Auth Recommendation** — Recommend `ANTHROPIC_AUTH_TOKEN`
over `ANTHROPIC_CUSTOM_HEADERS` for Claude Code authentication (#3686)
## Summary
This PR releases Bifrost OSS `v1.5.5` and Enterprise `v1.4.4`, bumping all module pins from `v1.5.12`/`v1.3.12` to `v1.5.13`/`v1.3.13` across core, framework, and all plugins. It also hardens the Docker manifest shell scripts, expands CI egress allowlists, and updates documentation to reflect the new SCIM-based user provisioning feature.
## Changes
- **Module version bumps**: All `go.mod`/`go.sum` files updated from `core v1.5.12` → `v1.5.13`, `framework v1.3.12` → `v1.3.13`, and all plugin versions incremented accordingly (`compat`, `governance`, `jsonparser`, `logging`, `maxim`, `mocker`, `otel`, `prompts`, `semanticcache`, `telemetry`).
- **Docker manifest scripts**: Added `#!/usr/bin/env bash` shebang and `set -euo pipefail` to `create-docker-manifest.sh` and `create-docker-manifest-ubi9.sh`; quoted all variable expansions and switched `jq -r` to `jq -er` to fail on null digests.
- **CI egress allowlist**: Added `production.cloudfront.docker.com:443` to Docker-related job allowlists, and added `_https._tcp.dl.google.com:443` and `motd.ubuntu.com:443` to the Ubuntu package job allowlist.
- **Changelog files**: Cleared per-module `changelog.md` files (content moved into the new versioned docs). Added `docs/changelogs/v1.5.5.mdx` and `docs/changelogs/ent-v1.4.4.mdx` with full release notes, and registered both in `docs/docs.json`.
- **Documentation**: Replaced the SSO Integration link with a User Provisioning (SCIM) link in both `README.md` and `transports/README.md`.
- **Enterprise v1.4.4 highlights** (documented): Kafka and Google Cloud Pub/Sub observability sinks, chunked streaming with a 100 MB inter-node message ceiling, BigQuery custom labels via env vars using the new `ConfigMarshallerPlugin` interface, temporary access token expiry extensions, and a multi-node cluster integration harness.
- **OSS v1.5.5 highlights** (documented): Azure v1 API migration, env-var support for OTel/Prometheus configs, OTel extra-header forwarding and semantic-convention alignment, virtual key quota including provider configs, Responses API streaming in `jsonparser`, and a batch of Bedrock, Gemini, Anthropic, Azure, and logging plugin fixes.
## Type of change
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [x] Documentation
- [x] Chore/CI
## Affected areas
- [x] Core (Go)
- [x] Transports (HTTP)
- [x] Providers/Integrations
- [x] Plugins
- [ ] UI (React)
- [x] Docs
## How to test
```sh
# Core/Transports
go version
go test ./...
# Verify Docker manifest scripts exit on error
bash -n .github/workflows/scripts/create-docker-manifest.sh
bash -n .github/workflows/scripts/create-docker-manifest-ubi9.sh
```
Validate that the new changelog pages (`changelogs/v1.5.5` and `changelogs/ent-v1.4.4`) render correctly in the docs site.
## Screenshots/Recordings
N/A
## Breaking changes
- [x] Yes
- [ ] No
The Azure provider no longer accepts `api_version` in `AzureKeyConfig` and has migrated to the `/openai/v1/{operation}` URL pattern. See the [v1.4.0 Migration Guide](https://docs.getbifrost.ai/enterprise/migration-guides/v1.4.0) for full details.
## Related issues
#3661, #3756, #3651, #3730, #3732, #3754, #3747, #3690, #3729, #3685, #3733, #3761, #3735, #3721, #3720, #3749, #3698, #3762, #3750, #3727, #3717, #3759, #3758, #3764, #3691, #3692, #3737, #3763
## Security considerations
- The `ConfigMarshallerPlugin` interface redacts secrets (OTel collector URLs, Prometheus push gateway credentials, BigQuery labels) at config storage time and rehydrates them at load time, preventing plaintext secret persistence.
- Docker manifest scripts now use `set -euo pipefail`, preventing silent failures that could result in malformed or missing image manifests being pushed.
## Checklist
- [x] I read `docs/contributing/README.md` and followed the guidelines
- [x] I added/updated tests where appropriate
- [x] I updated documentation where needed
- [x] I verified builds succeed (Go and UI)
- [x] I verified the CI pipeline passes locally if applicable
… OTEL and Prometheus plugin configs (maximhq#3651) ## Summary This PR introduces `EnvVar`-typed fields for sensitive and configurable URL/credential values in the OpenTelemetry and Prometheus (telemetry) plugins, replacing raw `string` fields. This allows users to reference environment variables (e.g., `env.OTEL_COLLECTOR_URL`) instead of embedding literal values in stored configuration, improving secret management and deployment flexibility. ## Changes - **`plugins/otel`**: `CollectorURL`, `MetricsEndpoint`, and `Headers` values in `Config` are now `*schemas.EnvVar` instead of `string`/`map[string]string`. Added `MarshalForStorage()` to serialize back to plain strings for DB persistence, `Redacted()` for safe API responses, and `resolveHeaders()` to convert `EnvVar` header maps to plain strings at runtime. Removed the inline `env.` prefix resolution loop from `Init` in favor of `EnvVar.GetValue()`. - **`plugins/telemetry`**: `PushGatewayURL`, `BasicAuth.Username`, and `BasicAuth.Password` in `PushGatewayConfig`/`BasicAuthConfig` are now `*schemas.EnvVar`. Added `MarshalForStorage()` and `Redacted()` to `Config` with the same storage/API separation pattern. - **`core/schemas`**: Introduced the `ConfigMarshallerPlugin` interface, optionally implemented by plugins that need custom config serialization. The server calls `MarshalConfigForStorage` before writing config to the DB and `RedactConfig` when building API responses. Both the OTEL and telemetry plugins implement this interface. - **`transports/bifrost-http/handlers/plugins.go`**: Added `normalizePluginConfig()` to round-trip plugin configs through their typed structs before DB writes (ensuring `EnvVar` → plain string serialization), and `expandPluginConfigForAPI()` to expand stored plain strings back into full `EnvVar` objects with redaction for API responses. Refactored `getPlugins` to use `buildPluginResponseWithStatuses` to avoid redundant status fetches per plugin. - **`transports/bifrost-http/lib/config.go`**: Added a `ConfigMarshallers` atomic cache derived from `BasePlugins`, rebuilt alongside the other interface caches on any plugin change. - **`transports/bifrost-http/server/server.go`**: Implemented `NormalizePluginConfig` and `ExpandPluginConfigForAPI` on `BifrostHTTPServer`, backed by the `ConfigMarshallers` cache. - **UI schemas (`ui/lib/types/schemas.ts`)**: Updated `otelConfigSchema` and `prometheusConfigSchema` to use `envVarSchema` for URL and credential fields. Validation logic now skips format checks for env var references (`from_env: true`) and checks `value` or `from_env` presence instead of raw string truthiness. - **UI forms**: Replaced plain `<Input>` components with `<EnvVarInput>` for collector URL, metrics endpoint, push gateway URL, and basic auth fields in the OTEL and Prometheus form fragments. The password field now uses `EnvVarInput` with `hideValueWhenEnv` and `redactNonEnvValue` props, removing the manual show/hide toggle. `HeadersTable` now uses `useEnvVarInput` mode. - **`ui/lib/utils/envVarForm.ts`**: Fixed `toEnvVarFormValue` to clear `value` when the input is an env reference string. Added `toEnvVarMapFormValue` to convert header maps of mixed `string | EnvVar` values into typed `EnvVar` form values. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... ./plugins/telemetry/... ./transports/bifrost-http/... # UI cd ui pnpm i pnpm build pnpm test ``` **Manual validation:** 1. Configure the OTEL plugin with `collector_url` set to `env.OTEL_COLLECTOR_URL` and verify the environment variable is resolved at runtime. 2. Set a literal URL and confirm it is stored and returned correctly. 3. Configure Prometheus push gateway with `env.PUSHGATEWAY_URL` and basic auth credentials via env vars; verify metrics are pushed correctly. 4. Confirm API responses show full `EnvVar` objects with sensitive values redacted. 5. Confirm DB-stored configs contain plain strings (`env.FOO` or literal values), not JSON objects. ## Breaking changes - [x] Yes - [ ] No The `Config` structs for the OTEL and telemetry plugins have changed field types from `string` to `*schemas.EnvVar`. Any code directly constructing these structs (e.g., in tests or custom integrations) must be updated to wrap values using `schemas.NewEnvVar(...)` or equivalent. Configs already stored in the database as plain strings will be transparently upgraded on read via `EnvVar.UnmarshalJSON`. ## Security considerations Sensitive fields (collector URLs, push gateway URLs, basic auth credentials, and OTEL headers) are now redacted in API responses via the `Redacted()` methods. Credentials are never stored as resolved values — only as `env.VAR_NAME` references or literal strings as provided by the user. The `FullyRedacted()` method is applied to passwords specifically. ## 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
## ✨ Features
- **Azure v1 API Migration** — Migrated Azure provider to the v1 API:
removed the `api-version` query parameter and the
`/openai/deployments/{model}/...` URL pattern in favor of
`/openai/v1/{operation}`; the `api_version` field has been dropped from
`AzureKeyConfig` (maximhq#3661, maximhq#3756)
- **EnvVar Support for OTEL & Prometheus Configs** — `CollectorURL`,
`MetricsEndpoint`, headers, push gateway URL, and basic auth credentials
can now be sourced from environment variables (e.g.,
`env.OTEL_COLLECTOR_URL`); added a new `ConfigMarshallerPlugin`
interface that lets plugins control storage/redaction round-trips
(maximhq#3651)
- **OTel Extra Header Forwarding** — `x-bf-eh-*` extra headers forwarded
to upstream providers are now also emitted on the request span under
`gen_ai.request.extra_header.*` for end-to-end tracing (maximhq#3730)
- **OTel Semantic Conventions** — Aligned OTel attribute keys with the
OpenTelemetry GenAI spec (canonical `gen_ai.*` and new `bifrost.*`
attributes); legacy attributes are retained in parallel to avoid
breaking existing dashboards (maximhq#3732)
- **VK Quota with Provider Configs** — `GetVirtualKeyQuotaByValue` and
the `getVirtualKeyQuota` HTTP response now include `provider_configs`
with their budgets and rate limits (maximhq#3721)
- **MCP Temp Token Non-Auth Toggle** — Added
`mcp_enable_temp_token_auth` client config flag to gate short-lived MCP
token minting for non-authenticated users (maximhq#3720)
- **Responses Stream in JSON Parser** — `jsonparser` plugin now handles
OpenAI Responses API streaming (`ResponsesStreamRequest`) in addition to
chat completions (maximhq#3749)
- **Session API Rework** — Logout now calls both the password-based
session logout and OAuth logout endpoints and resets all RTK Query cache
state (maximhq#3698)
## 🐞 Fixed
- **Streaming Latency for Observability** — Deferred root span
termination to the trace completer callback for streaming requests so
request latency is no longer inflated by header-flush time (maximhq#3762)
- **Stream Cancellation Race** — Set `BifrostContextKeyConnectionClosed`
before closing the stream and short-circuit `idleTimeoutReader.Read`
when the connection is already closed to avoid panics and hangs on
cancellation (maximhq#3733)
- **Bedrock Cache Points** — Strip cache points from Bedrock requests
for models that do not support prompt caching (e.g., GLM, Llama) to
avoid Converse API errors (maximhq#3754)
- **Bedrock Empty Text Blocks** — Skip empty/nil text blocks during
Bedrock response conversion to avoid invalid messages (maximhq#3747)
- **Bedrock Reasoning + Tools** — Preserve reasoning content blocks on
assistant turns that also contain tool calls in the Bedrock chat
converter (maximhq#3690)
- **Bedrock Search Content & Video** — Restored search content and video
parts that were being dropped from Bedrock-native passthrough requests
(maximhq#3729)
- **Structured Output Stop Reason** — Fixed an incorrect `tool_calls`
finish reason when structured output is combined with extended-thinking
tools (maximhq#3685)
- **Gemini Tool Schema Passthrough** — Forward full tool parameter
schemas via `parametersJsonSchema` instead of the lossy `parameters`
form; corrected tool response role to `user`; resolved structured output
+ tools conflict (maximhq#3761)
- **Anthropic Stop Reason & Tool Versions** — Normalized stop reason
mapping (`end_turn` to `stop`, `tool_use` to `tool_calls`, `max_tokens`
to `length`) and upgraded `text_editor_20250124`/`str_replace_editor` to
`text_editor_20250728` for computer-use tools (maximhq#3761)
- **Azure Endpoint Redaction** — Fixed a panic when
`AzureKeyConfig.Endpoint` is a literal value rather than an env
reference (maximhq#3761)
- **Auth Middleware Path Match** — Match temp-token auth middleware
whitelist against the request path only, not the full URI with query
parameters (maximhq#3737)
- **Governance Blocked Models UI** — Restored the missing Blocked Models
create/edit UI in the VK provider config sheet (maximhq#3750)
- **Logging Plugin Cleanup Drain** — Fixed a shutdown race where
`batchWriter` could drop in-flight log entries; `Cleanup` now drains
both the recovered batch and remaining queue within a 30-second budget
(maximhq#3717)
- **Model Rankings Empty Entries** — Excluded entries with empty `model`
values from model rankings matview queries so blank rows no longer
surface in the UI (maximhq#3758)
- **User Filter Duplicates** — Recreated `mv_filter_users` matview to
require non-empty `user_name`, eliminating duplicate filter dropdown
entries (maximhq#3764)
- **User Filter Display Name** — Use `user_name` instead of `user_id` as
the display label for users in logging filters (maximhq#3691)
- **Large Numeric ID Precision** — Preserve large numeric IDs in URL
search params by skipping JSON parsing for plain strings (maximhq#3692)
## 🔧 Refactors & Chores
- **Error Propagation for GetAvailable\* APIs** — `GetAvailable*`
methods on `LoggerPlugin`/`LogManager` now return wrapped errors instead
of silently logging and returning empty slices (maximhq#3759)
- **Governance Blocklist Matching** — Use `slices.Contains` for VK
blocked-model matching for clearer code with identical semantics (maximhq#3727)
- **Exported `ResolvePeriod`** — Renamed `resolvePeriod` to
`ResolvePeriod` so external packages can reuse the period parsing
(maximhq#3763)
## 📚 Docs
- **OTEL Env Var Documentation** — Documented `env.VAR_NAME` support for
`collector_url`, `metrics_endpoint`, and headers in OTEL/Prometheus
plugin docs
- **OTEL OSS Features & Examples** — Added OTEL documentation to the OSS
features list with usage examples (maximhq#3731)
- **Anthropic Auth Recommendation** — Recommend `ANTHROPIC_AUTH_TOKEN`
over `ANTHROPIC_CUSTOM_HEADERS` for Claude Code authentication (maximhq#3686)
… OTEL and Prometheus plugin configs (maximhq#3651) ## Summary This PR introduces `EnvVar`-typed fields for sensitive and configurable URL/credential values in the OpenTelemetry and Prometheus (telemetry) plugins, replacing raw `string` fields. This allows users to reference environment variables (e.g., `env.OTEL_COLLECTOR_URL`) instead of embedding literal values in stored configuration, improving secret management and deployment flexibility. ## Changes - **`plugins/otel`**: `CollectorURL`, `MetricsEndpoint`, and `Headers` values in `Config` are now `*schemas.EnvVar` instead of `string`/`map[string]string`. Added `MarshalForStorage()` to serialize back to plain strings for DB persistence, `Redacted()` for safe API responses, and `resolveHeaders()` to convert `EnvVar` header maps to plain strings at runtime. Removed the inline `env.` prefix resolution loop from `Init` in favor of `EnvVar.GetValue()`. - **`plugins/telemetry`**: `PushGatewayURL`, `BasicAuth.Username`, and `BasicAuth.Password` in `PushGatewayConfig`/`BasicAuthConfig` are now `*schemas.EnvVar`. Added `MarshalForStorage()` and `Redacted()` to `Config` with the same storage/API separation pattern. - **`core/schemas`**: Introduced the `ConfigMarshallerPlugin` interface, optionally implemented by plugins that need custom config serialization. The server calls `MarshalConfigForStorage` before writing config to the DB and `RedactConfig` when building API responses. Both the OTEL and telemetry plugins implement this interface. - **`transports/bifrost-http/handlers/plugins.go`**: Added `normalizePluginConfig()` to round-trip plugin configs through their typed structs before DB writes (ensuring `EnvVar` → plain string serialization), and `expandPluginConfigForAPI()` to expand stored plain strings back into full `EnvVar` objects with redaction for API responses. Refactored `getPlugins` to use `buildPluginResponseWithStatuses` to avoid redundant status fetches per plugin. - **`transports/bifrost-http/lib/config.go`**: Added a `ConfigMarshallers` atomic cache derived from `BasePlugins`, rebuilt alongside the other interface caches on any plugin change. - **`transports/bifrost-http/server/server.go`**: Implemented `NormalizePluginConfig` and `ExpandPluginConfigForAPI` on `BifrostHTTPServer`, backed by the `ConfigMarshallers` cache. - **UI schemas (`ui/lib/types/schemas.ts`)**: Updated `otelConfigSchema` and `prometheusConfigSchema` to use `envVarSchema` for URL and credential fields. Validation logic now skips format checks for env var references (`from_env: true`) and checks `value` or `from_env` presence instead of raw string truthiness. - **UI forms**: Replaced plain `<Input>` components with `<EnvVarInput>` for collector URL, metrics endpoint, push gateway URL, and basic auth fields in the OTEL and Prometheus form fragments. The password field now uses `EnvVarInput` with `hideValueWhenEnv` and `redactNonEnvValue` props, removing the manual show/hide toggle. `HeadersTable` now uses `useEnvVarInput` mode. - **`ui/lib/utils/envVarForm.ts`**: Fixed `toEnvVarFormValue` to clear `value` when the input is an env reference string. Added `toEnvVarMapFormValue` to convert header maps of mixed `string | EnvVar` values into typed `EnvVar` form values. ## Type of change - [ ] Bug fix - [x] Feature - [ ] Refactor - [ ] Documentation - [ ] Chore/CI ## Affected areas - [ ] Core (Go) - [x] Transports (HTTP) - [ ] Providers/Integrations - [x] Plugins - [x] UI (React) - [ ] Docs ## How to test ```sh # Core/Transports go test ./plugins/otel/... ./plugins/telemetry/... ./transports/bifrost-http/... # UI cd ui pnpm i pnpm build pnpm test ``` **Manual validation:** 1. Configure the OTEL plugin with `collector_url` set to `env.OTEL_COLLECTOR_URL` and verify the environment variable is resolved at runtime. 2. Set a literal URL and confirm it is stored and returned correctly. 3. Configure Prometheus push gateway with `env.PUSHGATEWAY_URL` and basic auth credentials via env vars; verify metrics are pushed correctly. 4. Confirm API responses show full `EnvVar` objects with sensitive values redacted. 5. Confirm DB-stored configs contain plain strings (`env.FOO` or literal values), not JSON objects. ## Breaking changes - [x] Yes - [ ] No The `Config` structs for the OTEL and telemetry plugins have changed field types from `string` to `*schemas.EnvVar`. Any code directly constructing these structs (e.g., in tests or custom integrations) must be updated to wrap values using `schemas.NewEnvVar(...)` or equivalent. Configs already stored in the database as plain strings will be transparently upgraded on read via `EnvVar.UnmarshalJSON`. ## Security considerations Sensitive fields (collector URLs, push gateway URLs, basic auth credentials, and OTEL headers) are now redacted in API responses via the `Redacted()` methods. Credentials are never stored as resolved values — only as `env.VAR_NAME` references or literal strings as provided by the user. The `FullyRedacted()` method is applied to passwords specifically. ## 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
## ✨ Features
- **Azure v1 API Migration** — Migrated Azure provider to the v1 API:
removed the `api-version` query parameter and the
`/openai/deployments/{model}/...` URL pattern in favor of
`/openai/v1/{operation}`; the `api_version` field has been dropped from
`AzureKeyConfig` (maximhq#3661, maximhq#3756)
- **EnvVar Support for OTEL & Prometheus Configs** — `CollectorURL`,
`MetricsEndpoint`, headers, push gateway URL, and basic auth credentials
can now be sourced from environment variables (e.g.,
`env.OTEL_COLLECTOR_URL`); added a new `ConfigMarshallerPlugin`
interface that lets plugins control storage/redaction round-trips
(maximhq#3651)
- **OTel Extra Header Forwarding** — `x-bf-eh-*` extra headers forwarded
to upstream providers are now also emitted on the request span under
`gen_ai.request.extra_header.*` for end-to-end tracing (maximhq#3730)
- **OTel Semantic Conventions** — Aligned OTel attribute keys with the
OpenTelemetry GenAI spec (canonical `gen_ai.*` and new `bifrost.*`
attributes); legacy attributes are retained in parallel to avoid
breaking existing dashboards (maximhq#3732)
- **VK Quota with Provider Configs** — `GetVirtualKeyQuotaByValue` and
the `getVirtualKeyQuota` HTTP response now include `provider_configs`
with their budgets and rate limits (maximhq#3721)
- **MCP Temp Token Non-Auth Toggle** — Added
`mcp_enable_temp_token_auth` client config flag to gate short-lived MCP
token minting for non-authenticated users (maximhq#3720)
- **Responses Stream in JSON Parser** — `jsonparser` plugin now handles
OpenAI Responses API streaming (`ResponsesStreamRequest`) in addition to
chat completions (maximhq#3749)
- **Session API Rework** — Logout now calls both the password-based
session logout and OAuth logout endpoints and resets all RTK Query cache
state (maximhq#3698)
## 🐞 Fixed
- **Streaming Latency for Observability** — Deferred root span
termination to the trace completer callback for streaming requests so
request latency is no longer inflated by header-flush time (maximhq#3762)
- **Stream Cancellation Race** — Set `BifrostContextKeyConnectionClosed`
before closing the stream and short-circuit `idleTimeoutReader.Read`
when the connection is already closed to avoid panics and hangs on
cancellation (maximhq#3733)
- **Bedrock Cache Points** — Strip cache points from Bedrock requests
for models that do not support prompt caching (e.g., GLM, Llama) to
avoid Converse API errors (maximhq#3754)
- **Bedrock Empty Text Blocks** — Skip empty/nil text blocks during
Bedrock response conversion to avoid invalid messages (maximhq#3747)
- **Bedrock Reasoning + Tools** — Preserve reasoning content blocks on
assistant turns that also contain tool calls in the Bedrock chat
converter (maximhq#3690)
- **Bedrock Search Content & Video** — Restored search content and video
parts that were being dropped from Bedrock-native passthrough requests
(maximhq#3729)
- **Structured Output Stop Reason** — Fixed an incorrect `tool_calls`
finish reason when structured output is combined with extended-thinking
tools (maximhq#3685)
- **Gemini Tool Schema Passthrough** — Forward full tool parameter
schemas via `parametersJsonSchema` instead of the lossy `parameters`
form; corrected tool response role to `user`; resolved structured output
+ tools conflict (maximhq#3761)
- **Anthropic Stop Reason & Tool Versions** — Normalized stop reason
mapping (`end_turn` to `stop`, `tool_use` to `tool_calls`, `max_tokens`
to `length`) and upgraded `text_editor_20250124`/`str_replace_editor` to
`text_editor_20250728` for computer-use tools (maximhq#3761)
- **Azure Endpoint Redaction** — Fixed a panic when
`AzureKeyConfig.Endpoint` is a literal value rather than an env
reference (maximhq#3761)
- **Auth Middleware Path Match** — Match temp-token auth middleware
whitelist against the request path only, not the full URI with query
parameters (maximhq#3737)
- **Governance Blocked Models UI** — Restored the missing Blocked Models
create/edit UI in the VK provider config sheet (maximhq#3750)
- **Logging Plugin Cleanup Drain** — Fixed a shutdown race where
`batchWriter` could drop in-flight log entries; `Cleanup` now drains
both the recovered batch and remaining queue within a 30-second budget
(maximhq#3717)
- **Model Rankings Empty Entries** — Excluded entries with empty `model`
values from model rankings matview queries so blank rows no longer
surface in the UI (maximhq#3758)
- **User Filter Duplicates** — Recreated `mv_filter_users` matview to
require non-empty `user_name`, eliminating duplicate filter dropdown
entries (maximhq#3764)
- **User Filter Display Name** — Use `user_name` instead of `user_id` as
the display label for users in logging filters (maximhq#3691)
- **Large Numeric ID Precision** — Preserve large numeric IDs in URL
search params by skipping JSON parsing for plain strings (maximhq#3692)
## 🔧 Refactors & Chores
- **Error Propagation for GetAvailable\* APIs** — `GetAvailable*`
methods on `LoggerPlugin`/`LogManager` now return wrapped errors instead
of silently logging and returning empty slices (maximhq#3759)
- **Governance Blocklist Matching** — Use `slices.Contains` for VK
blocked-model matching for clearer code with identical semantics (maximhq#3727)
- **Exported `ResolvePeriod`** — Renamed `resolvePeriod` to
`ResolvePeriod` so external packages can reuse the period parsing
(maximhq#3763)
## 📚 Docs
- **OTEL Env Var Documentation** — Documented `env.VAR_NAME` support for
`collector_url`, `metrics_endpoint`, and headers in OTEL/Prometheus
plugin docs
- **OTEL OSS Features & Examples** — Added OTEL documentation to the OSS
features list with usage examples (maximhq#3731)
- **Anthropic Auth Recommendation** — Recommend `ANTHROPIC_AUTH_TOKEN`
over `ANTHROPIC_CUSTOM_HEADERS` for Claude Code authentication (maximhq#3686)

Summary
This PR introduces
EnvVar-typed fields for sensitive and configurable URL/credential values in the OpenTelemetry and Prometheus (telemetry) plugins, replacing rawstringfields. This allows users to reference environment variables (e.g.,env.OTEL_COLLECTOR_URL) instead of embedding literal values in stored configuration, improving secret management and deployment flexibility.Changes
plugins/otel:CollectorURL,MetricsEndpoint, andHeadersvalues inConfigare now*schemas.EnvVarinstead ofstring/map[string]string. AddedMarshalForStorage()to serialize back to plain strings for DB persistence,Redacted()for safe API responses, andresolveHeaders()to convertEnvVarheader maps to plain strings at runtime. Removed the inlineenv.prefix resolution loop fromInitin favor ofEnvVar.GetValue().plugins/telemetry:PushGatewayURL,BasicAuth.Username, andBasicAuth.PasswordinPushGatewayConfig/BasicAuthConfigare now*schemas.EnvVar. AddedMarshalForStorage()andRedacted()toConfigwith the same storage/API separation pattern.core/schemas: Introduced theConfigMarshallerPlugininterface, optionally implemented by plugins that need custom config serialization. The server callsMarshalConfigForStoragebefore writing config to the DB andRedactConfigwhen building API responses. Both the OTEL and telemetry plugins implement this interface.transports/bifrost-http/handlers/plugins.go: AddednormalizePluginConfig()to round-trip plugin configs through their typed structs before DB writes (ensuringEnvVar→ plain string serialization), andexpandPluginConfigForAPI()to expand stored plain strings back into fullEnvVarobjects with redaction for API responses. RefactoredgetPluginsto usebuildPluginResponseWithStatusesto avoid redundant status fetches per plugin.transports/bifrost-http/lib/config.go: Added aConfigMarshallersatomic cache derived fromBasePlugins, rebuilt alongside the other interface caches on any plugin change.transports/bifrost-http/server/server.go: ImplementedNormalizePluginConfigandExpandPluginConfigForAPIonBifrostHTTPServer, backed by theConfigMarshallerscache.ui/lib/types/schemas.ts): UpdatedotelConfigSchemaandprometheusConfigSchemato useenvVarSchemafor URL and credential fields. Validation logic now skips format checks for env var references (from_env: true) and checksvalueorfrom_envpresence instead of raw string truthiness.<Input>components with<EnvVarInput>for collector URL, metrics endpoint, push gateway URL, and basic auth fields in the OTEL and Prometheus form fragments. The password field now usesEnvVarInputwithhideValueWhenEnvandredactNonEnvValueprops, removing the manual show/hide toggle.HeadersTablenow usesuseEnvVarInputmode.ui/lib/utils/envVarForm.ts: FixedtoEnvVarFormValueto clearvaluewhen the input is an env reference string. AddedtoEnvVarMapFormValueto convert header maps of mixedstring | EnvVarvalues into typedEnvVarform values.Type of change
Affected areas
How to test
Manual validation:
collector_urlset toenv.OTEL_COLLECTOR_URLand verify the environment variable is resolved at runtime.env.PUSHGATEWAY_URLand basic auth credentials via env vars; verify metrics are pushed correctly.EnvVarobjects with sensitive values redacted.env.FOOor literal values), not JSON objects.Breaking changes
The
Configstructs for the OTEL and telemetry plugins have changed field types fromstringto*schemas.EnvVar. Any code directly constructing these structs (e.g., in tests or custom integrations) must be updated to wrap values usingschemas.NewEnvVar(...)or equivalent. Configs already stored in the database as plain strings will be transparently upgraded on read viaEnvVar.UnmarshalJSON.Security considerations
Sensitive fields (collector URLs, push gateway URLs, basic auth credentials, and OTEL headers) are now redacted in API responses via the
Redacted()methods. Credentials are never stored as resolved values — only asenv.VAR_NAMEreferences or literal strings as provided by the user. TheFullyRedacted()method is applied to passwords specifically.Checklist
docs/contributing/README.mdand followed the guidelines