Skip to content

feat(pricing): implement scoped pricing overrides#1825

Open
jerkeyray wants to merge 7 commits intographite-base/1825from
feat/scoped-pricing-overrides
Open

feat(pricing): implement scoped pricing overrides#1825
jerkeyray wants to merge 7 commits intographite-base/1825from
feat/scoped-pricing-overrides

Conversation

@jerkeyray
Copy link
Contributor

@jerkeyray jerkeyray commented Feb 27, 2026

Changes

  • Added dedicated pricing override domain model + validation in Go schemas.
  • Added persistent storage for overrides in config store with migration/reconcile logic.
  • Added governance HTTP CRUD endpoints for pricing overrides.
  • Added model catalog compile/resolve/apply flow for scoped overrides with clear precedence:
    • virtual_key_provider_key > virtual_key_provider > virtual_key > provider_key > provider > global
  • Removed regex match support; matching is now only exact and wildcard for simpler, safer behavior.
  • Added/updated UI for custom pricing overrides:
    • create/edit/delete flows
    • scope-aware form behavior
    • improved error handling (toast + inline load errors)
    • simplified table columns for better readability
  • Added name field for overrides end-to-end (schema, storage, API, UI).
  • Cleaned unrelated changes and aligned request payload semantics (patch only).

Type of change

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

Affected areas

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

How to test

Describe the steps to validate this change. Include commands and expected outcomes.

Core / Transports / Plugins

go version
go test ./core/schemas/...
go test ./framework/modelcatalog/... ./framework/configstore/... ./transports/bifrost-http/handlers/...

UI

cd ui
npm i
npx tsc --noEmit
npx next build --no-lint

Functional validation (API + behavior)

List overrides

curl -sS "http://localhost:3000/api/governance/pricing-overrides" | jq

Create global override

curl -sS -X POST "http://localhost:3000/api/governance/pricing-overrides" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "name": "Global GPT-5 mini",
    "scope_kind": "global",
    "match_type": "exact",
    "pattern": "gpt-5-mini",
    "patch": {
      "input_cost_per_token": 0.000001,
      "output_cost_per_token": 0.000002
    }
  }' | jq

Create provider_key override

curl -sS -X POST "http://localhost:3000/api/governance/pricing-overrides" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "name": "OpenAI key override",
    "scope_kind": "provider_key",
    "provider_key_id": "<PROVIDER_KEY_ID>",
    "match_type": "exact",
    "pattern": "gpt-5-mini",
    "patch": {
      "output_cost_per_token": 0.000003
    }
  }' | jq

Patch override

curl -sS -X PATCH "http://localhost:3000/api/governance/pricing-overrides/<OVERRIDE_ID>" \
  -H "Content-Type: application/json" \
  --data-raw '{
    "name": "Updated override",
    "patch": {
      "output_cost_per_token": 0.000009
    }
  }' | jq

Delete override

curl -sS -X DELETE "http://localhost:3000/api/governance/pricing-overrides/<OVERRIDE_ID>" | jq

Expected outcomes

  • CRUD endpoints succeed with valid scope payloads.
  • Invalid scope combinations return clear 4xx validation errors.
  • Scoped precedence resolves expected winning override.
  • UI table and drawer operations work across scope types.

No new configs or environment variables were added.


Screenshots / Recordings


Breaking changes

  • Yes
  • No

Impact and migration instructions

  • Legacy provider-level pricing override path has been removed in favor of scoped pricing overrides.
  • Regex match type is removed; existing patterns must use exact or trailing * wildcard.
  • Migration/reconcile path rebuilds pricing override storage for this pre-release feature path.

Related issues

Implements scoped pricing overrides feature branch and stacks on top of PR #1800 (02-26-refactor_pricing_module_refactor).


Security considerations

  • Scope validation is strict server-side; invalid identifier combinations are rejected.
  • No new secrets or auth bypass paths introduced.
  • Override mutation endpoints continue to rely on existing governance and transport auth controls.

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

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 27, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ec78e61 and 8c3506d.

📒 Files selected for processing (1)
  • ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx

📝 Walkthrough

Summary by CodeRabbit

  • New Features

    • Governance CRUD for scoped pricing overrides (UI + API) with scope filters, create/edit drawer, list view, and validation.
  • Enhancements

    • Runtime scoped pricing resolution (virtual key/provider/provider-key/global) and expanded pricing model: per-character, 128k-tier rates, image-token and cache costs.
  • Deprecations

    • Provider-level pricing overrides removed; use governance-scoped overrides instead.
  • Migrations

    • Database reconciliation and new extended pricing columns added.

Walkthrough

Replaces provider-level pricing overrides with a governance-managed, scope-aware pricing overrides system: adds schema/types, DB table and migrations, CRUD HTTP API, model-catalog scoped lookup/apply logic, expanded pricing columns, UI, and scope-aware cost calculations.

Changes

Cohort / File(s) Summary
Core Schema
core/schemas/pricing_overrides.go
Adds pricing override enums/types, PricingOverride and PricingOverridePatch, validators and normalization helpers.
Provider Config Removal
core/schemas/provider.go, framework/configstore/tables/provider.go, transports/config.schema.json, transports/bifrost-http/handlers/providers.go
Removes provider-level ProviderPricingOverride and pricing_overrides from schema, DB model, API responses and validation; request payloads rejecting unsupported field.
DB Migrations & Table
framework/configstore/migrations.go, framework/configstore/tables/pricingoverride.go
Adds reconciliation migration and new governance_pricing_overrides table model TablePricingOverride with BeforeSave/AfterFind hooks, JSON (de)serialization, and reconciliation/drop logic.
Config Store API & RDB
framework/configstore/store.go, framework/configstore/rdb.go, framework/configstore/clientconfig.go
Adds PricingOverrideFilter and CRUD methods on ConfigStore/RDB (Get/Create/Update/Delete pricing overrides); removes PricingOverrides from ProviderConfig, redaction and hashing.
ModelCatalog: scoped overrides
framework/modelcatalog/overrides.go, framework/modelcatalog/main.go, framework/modelcatalog/overrides_test.go
Implements compiled scoped overrides, Set/Upsert/Delete APIs, loader from store, scoped matching (virtual key/provider/provider key), patch application, and updated tests.
Pricing model expansion
framework/configstore/tables/modelpricing.go, framework/modelcatalog/utils.go, framework/modelcatalog/pricing.go
Converts many pricing fields to nullable pointers; adds character costs, 128k-tier fields, image-token and cache read costs; threads PricingLookupScopes through pricing resolution and calculations.
Migrations: extended pricing columns
framework/configstore/migrations.go
Adds migration to extend governance_model_pricing with new optional cost columns and rollback/backfill support.
Governance API & Handlers
transports/bifrost-http/handlers/governance.go, transports/bifrost-http/handlers/governance_pricing_overrides.go, transports/bifrost-http/server/server.go
Adds governance pricing-overrides HTTP handlers (GET/POST/PATCH/DELETE), validation, conversion, refresh logic; wires ModelCatalog into GovernanceHandler and server routes.
UI: overrides management
ui/app/workspace/custom-pricing/overrides/*, ui/lib/store/apis/governanceApi.ts, ui/lib/store/apis/baseApi.ts, ui/lib/types/governance.ts
New overrides page, drawer and view; RTK Query endpoints/hooks; governance types for overrides; adds UI routes and store tags.
UI: provider fragment removal & types
ui/app/workspace/providers/fragments/*, ui/lib/types/config.ts, ui/lib/types/schemas.ts
Removes provider-level pricing overrides UI fragment and related types/schemas and request fields.
Plugins & Observability
plugins/governance/main.go, plugins/logging/*, plugins/telemetry/main.go
Updates hooks to call CalculateCostWithScopes, passing VirtualKeyID/ProviderKeyID/ProviderID to enable scoped cost attribution.
Tests & Mocks
transports/bifrost-http/lib/config_test.go, framework/modelcatalog/*_test.go
Adds mock ConfigStore pricing-override methods and expands modelcatalog tests for scoped override precedence and tie-breaking.
UI Navigation & Misc
ui/components/sidebar.tsx, ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
Adds "Pricing overrides" sidebar route and a route-match helper; minor formatting tweaks.

Sequence Diagram(s)

sequenceDiagram
  participant Browser as "Browser / UI"
  participant API as "Governance API"
  participant DB as "ConfigStore (DB)"
  participant Catalog as "ModelCatalog"
  participant Plugin as "Plugin (Logging/Telemetry)"

  Browser->>API: POST /governance/pricing-overrides (create)
  API->>DB: CreatePricingOverride(row)
  DB-->>API: Created (id)
  API->>Catalog: refresh/loadPricingOverridesFromStore()
  Catalog->>DB: GetPricingOverrides(filter)
  DB-->>Catalog: list of overrides
  Catalog->>Catalog: compileScopedOverrides()
  Catalog-->>API: ack created
  Note over Plugin,Catalog: During request handling
  Plugin->>Catalog: CalculateCostWithScopes(result, scopes)
  Catalog->>Catalog: resolveScopedOverride(scopes, model, requestType)
  Catalog-->>Plugin: patched pricing entry (cost)
  Plugin-->>Browser: metrics/log updated
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • akshaydeo

Poem

🐰 I nibble through schemas, patterns neat,
I hop through scopes where virtual keys meet,
Tokens, images, chars — costs now refined,
Overrides compiled and eager to find,
A tiny rabbit cheers this scoped design.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 23.75% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat(pricing): implement scoped pricing overrides' clearly and directly describes the primary change: implementing a feature for scoped pricing overrides.
Description check ✅ Passed The PR description addresses most required template sections: comprehensive Changes, clear Type of change selections, specific Affected areas, detailed How to test steps with commands, explicit Breaking changes acknowledgment, and completed Checklist. Minor sections like Screenshots/Recordings are appropriately omitted as non-applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feat/scoped-pricing-overrides

Tip

Try Coding Plans. Let us write the prompt for your AI agent so you can ship faster (with fewer bugs).
Share your feedback on Discord.


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

Copy link
Contributor Author

jerkeyray commented Feb 27, 2026

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.

@jerkeyray jerkeyray changed the title feat: implement scoped pricing override feat(pricing): implement scoped pricing overrides Feb 27, 2026
@jerkeyray jerkeyray marked this pull request as ready for review February 27, 2026 17:18
Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 8

Caution

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

⚠️ Outside diff range comments (1)
transports/bifrost-http/handlers/governance.go (1)

52-63: ⚠️ Potential issue | 🟠 Major

Fail fast if modelCatalog is missing.

modelCatalog is now a constructor dependency for governance pricing override flows; allowing nil here risks runtime panics later. Add an explicit constructor guard.

💡 Suggested patch
 func NewGovernanceHandler(manager GovernanceManager, configStore configstore.ConfigStore, modelCatalog *modelcatalog.ModelCatalog) (*GovernanceHandler, error) {
 	if manager == nil {
 		return nil, fmt.Errorf("governance manager is required")
 	}
 	if configStore == nil {
 		return nil, fmt.Errorf("config store is required")
 	}
+	if modelCatalog == nil {
+		return nil, fmt.Errorf("model catalog is required")
+	}
 	return &GovernanceHandler{
 		governanceManager: manager,
 		configStore:       configStore,
 		modelCatalog:      modelCatalog,
 	}, nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/governance.go` around lines 52 - 63,
NewGovernanceHandler currently allows a nil modelCatalog which can cause runtime
panics; update the constructor (NewGovernanceHandler) to validate modelCatalog
and return an error if it's nil (similar to existing checks for manager and
configStore) so GovernanceHandler is never created with a nil modelCatalog.
Ensure the error message is descriptive (e.g., "model catalog is required") and
keep the returned type and structure unchanged.
🧹 Nitpick comments (6)
transports/bifrost-http/handlers/providers.go (1)

187-187: Optional: deduplicate the repeated rejection message constant.

The same error string appears in both handlers; moving it to a shared const will prevent drift.

Also applies to: 324-324

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/handlers/providers.go` at line 187, The rejection
message "pricing_overrides is not a supported provider field; use
/api/governance/pricing-overrides" is duplicated; introduce a shared constant
(e.g., ErrPricingOverridesNotSupported) in
transports/bifrost-http/handlers/providers.go and replace the literal strings in
both handler functions that call SendError (the occurrences around the existing
callers at the previously noted spots) with that constant to avoid drift.
ui/components/sidebar.tsx (1)

195-198: Consider centralizing the custom-pricing route matcher.

The same special-case matcher is duplicated in multiple places. Extracting one shared helper will reduce drift risk.

♻️ Proposed refactor
+const matchWorkspaceRoute = (pathname: string, url: string) => {
+	if (url === "/workspace/custom-pricing") return pathname === url;
+	return pathname.startsWith(url);
+};
...
-const isRouteMatch = (url: string) => {
-	if (url === "/workspace/custom-pricing") return pathname === url;
-	return pathname.startsWith(url);
-};
+const isRouteMatch = (url: string) => matchWorkspaceRoute(pathname, url);
...
-const isRouteMatch = (url: string) => {
-	if (url === "/workspace/custom-pricing") return pathname === url;
-	return pathname.startsWith(url);
-};
+const isRouteMatch = (url: string) => matchWorkspaceRoute(pathname, url);

Also applies to: 773-776

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/components/sidebar.tsx` around lines 195 - 198, The route matcher
special-casing "/workspace/custom-pricing" is duplicated; extract a single
shared helper (e.g., export function isWorkspaceRouteMatch(url: string,
pathname: string)) and replace local isRouteMatch implementations with calls to
that helper; update usages that reference the literal
"/workspace/custom-pricing" and the local pathname variable (including the other
duplicated locations around the second occurrence) so all route checks use the
shared function to avoid drift.
ui/lib/types/governance.ts (1)

391-419: Consider tightening request_types from string[] to a constrained request-type union.

Using string[] here allows invalid values through the type system and pushes failures to API-time 4xx.

💡 Suggested refinement
+export type PricingOverrideRequestType =
+	| "chat_completion"
+	| "text_completion"
+	| "embedding"
+	| "rerank"
+	| "responses"
+	| "speech"
+	| "transcription"
+	| "image_generation"
+	| "video_generation";

 export interface PricingOverride {
 	...
-	request_types?: string[];
+	request_types?: PricingOverrideRequestType[];
 	...
 }

 export interface CreatePricingOverrideRequest {
 	...
-	request_types?: string[];
+	request_types?: PricingOverrideRequestType[];
 	...
 }

 export interface PatchPricingOverrideRequest {
 	...
-	request_types?: string[];
+	request_types?: PricingOverrideRequestType[];
 	...
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/lib/types/governance.ts` around lines 391 - 419, Tighten the request_types
fields by replacing the broad string[] with a constrained union type (e.g., type
RequestType = "chat" | "completion" | "embeddings" | ...) and use RequestType[]
for both CreatePricingOverrideRequest.request_types and
PatchPricingOverrideRequest.request_types; add or reuse an existing RequestType
union/enum in this module and update any code that constructs or validates these
requests to use the new type so invalid values are caught at compile time (refer
to the CreatePricingOverrideRequest and PatchPricingOverrideRequest interfaces
and the request_types property).
ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx (1)

22-23: Use relative ui/lib imports in this TSX file to match repository conventions.

These imports currently use alias paths where the guideline requests relative imports from ui/lib for utilities/types.

♻️ Proposed fix
 } from "@/lib/store";
-import { PricingOverride, PricingOverrideScopeKind } from "@/lib/types/governance";
+} from "../../../../lib/store";
+import { PricingOverride, PricingOverrideScopeKind } from "../../../../lib/types/governance";
As per coding guidelines: `ui/**/*.{ts,tsx}` should use relative imports from the `ui/lib` directory for constants, utilities, and types.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx`
around lines 22 - 23, The imports in scopedPricingOverridesView.tsx use alias
paths "@/lib/store" and "@/lib/types/governance" but should use relative ui/lib
imports per repo convention; update the import statements that bring in store
utilities (from "@/lib/store") and the types PricingOverride and
PricingOverrideScopeKind (from "@/lib/types/governance") to use relative paths
into the ui/lib directory (matching other ui/* files) so the file imports store
utilities and those types via the ui/lib relative modules instead of the "@/..."
aliases.
transports/bifrost-http/lib/config_test.go (1)

840-858: Make pricing override mock CRUD stateful to avoid low-signal tests.

These methods currently always return empty/not-found/no-op, so tests using MockConfigStore cannot validate create/update/delete/read behavior for overrides.

Proposed refactor
 type MockConfigStore struct {
   clientConfig     *configstore.ClientConfig
   providers        map[schemas.ModelProvider]configstore.ProviderConfig
+  pricingOverrides map[string]tables.TablePricingOverride
   mcpConfig        *schemas.MCPConfig
   governanceConfig *configstore.GovernanceConfig
@@
 func NewMockConfigStore() *MockConfigStore {
   return &MockConfigStore{
-    providers: make(map[schemas.ModelProvider]configstore.ProviderConfig),
+    providers:        make(map[schemas.ModelProvider]configstore.ProviderConfig),
+    pricingOverrides: make(map[string]tables.TablePricingOverride),
   }
 }
@@
 func (m *MockConfigStore) GetPricingOverrides(ctx context.Context, filter configstore.PricingOverrideFilter) ([]tables.TablePricingOverride, error) {
-  return []tables.TablePricingOverride{}, nil
+  overrides := make([]tables.TablePricingOverride, 0, len(m.pricingOverrides))
+  for _, o := range m.pricingOverrides {
+    overrides = append(overrides, o)
+  }
+  return overrides, nil
 }
 
 func (m *MockConfigStore) GetPricingOverrideByID(ctx context.Context, id string) (*tables.TablePricingOverride, error) {
-  return nil, configstore.ErrNotFound
+  o, ok := m.pricingOverrides[id]
+  if !ok {
+    return nil, configstore.ErrNotFound
+  }
+  return &o, nil
 }
 
 func (m *MockConfigStore) CreatePricingOverride(ctx context.Context, override *tables.TablePricingOverride, tx ...*gorm.DB) error {
+  m.pricingOverrides[override.ID] = *override
   return nil
 }
 
 func (m *MockConfigStore) UpdatePricingOverride(ctx context.Context, override *tables.TablePricingOverride, tx ...*gorm.DB) error {
+  if _, ok := m.pricingOverrides[override.ID]; !ok {
+    return configstore.ErrNotFound
+  }
+  m.pricingOverrides[override.ID] = *override
   return nil
 }
 
 func (m *MockConfigStore) DeletePricingOverride(ctx context.Context, id string, tx ...*gorm.DB) error {
+  delete(m.pricingOverrides, id)
   return nil
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@transports/bifrost-http/lib/config_test.go` around lines 840 - 858, The
MockConfigStore's pricing override methods (GetPricingOverrides,
GetPricingOverrideByID, CreatePricingOverride, UpdatePricingOverride,
DeletePricingOverride) are currently no-ops; modify MockConfigStore to hold an
in-memory, concurrency-safe map (e.g., map[string]tables.TablePricingOverride)
and a sync.Mutex or RWMutex, then implement CreatePricingOverride to insert a
copy into the map (generate or use override.ID), GetPricingOverrides to return
the slice of values, GetPricingOverrideByID to return the entry or
configstore.ErrNotFound, UpdatePricingOverride to replace an existing entry
(return ErrNotFound if missing), and DeletePricingOverride to remove the key
(return ErrNotFound if missing); ensure methods return copies (not pointers into
the map) and ignore the variadic tx parameter.
ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx (1)

21-31: Align ui/lib imports with the repository import-path rule.

This file imports constants/utilities/types via alias paths; the active guideline for UI files asks for relative imports from ui/lib.

As per coding guidelines ui/**/*.{ts,tsx}: “Use relative imports from the ui/lib directory for constants, utilities, and types.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx` around
lines 21 - 31, The imports using the "@/lib/..." alias (RequestTypeLabels,
ModelProvider, CreatePricingOverrideRequest, PatchPricingOverrideRequest,
PricingOverride, PricingOverrideMatchType, PricingOverridePatch,
PricingOverrideScopeKind, and cn) must be changed to use relative imports from
the ui/lib directory per the UI import rule; locate the import block in
pricingOverrideDrawer.tsx and replace each "@/lib/..." import with the
corresponding relative path into ui/lib (e.g., import RequestTypeLabels from the
appropriate relative ui/lib/constants/logs, ModelProvider from
ui/lib/types/config, the governance types from ui/lib/types/governance, and cn
from ui/lib/utils) so the file no longer uses the "@/lib" alias.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@core/schemas/pricing_overrides.go`:
- Around line 217-263: ValidatePricingOverrideScopeKind currently only trims IDs
for validation via normalizeOptionalID but doesn't return or apply the
normalized values, so callers may persist raw, untrimmed IDs; fix by either
changing ValidatePricingOverrideScopeKind (or adding a new helper like
NormalizePricingOverrideScopeIDs) to return the normalized virtualKeyID,
providerID, and providerKeyID alongside the error (e.g., return (*string,
*string, *string, error)) or update all persistence/compile call sites to call
normalizeOptionalID on virtualKeyID, providerID, providerKeyID before
constructing keys/records; reference the functions
ValidatePricingOverrideScopeKind and normalizeOptionalID and ensure the
normalized values replace the raw values before any downstream key construction
or storage.

In `@framework/modelcatalog/main.go`:
- Around line 265-267: The force-reload path currently swallows errors from
mc.loadPricingOverridesFromStore (it only logs mc.logger.Warn and continues),
allowing ForceReloadPricing to report success while scoped overrides may be
stale; change ForceReloadPricing to propagate the error from
mc.loadPricingOverridesFromStore instead of just logging it: detect the non-nil
error returned by mc.loadPricingOverridesFromStore(ctx) and return that error
(or wrap it with context) so callers receive failure; update any callers/tests
expecting success accordingly to handle the propagated error.

In `@framework/modelcatalog/pricing.go`:
- Around line 62-67: computeCacheEmbeddingCost can miss provider-scoped
overrides because it calls getPricingWithScopes without backfilling ProviderID
the way resolvePricing does; update computeCacheEmbeddingCost to populate the
scopes' ProviderID from cacheDebug.ProviderUsed (or delegate to resolvePricing)
before calling getPricingWithScopes so provider-scoped pricing is applied
correctly; reference the computeCacheEmbeddingCost function, the
getPricingWithScopes call, and the BifrostCacheDebug fields ProviderUsed and
ModelUsed when making the change.

In `@framework/modelcatalog/utils.go`:
- Around line 47-48: normalizeStreamRequestType currently doesn't map
schemas.ImageEditStreamRequest to the image generation category, causing stream
normalization to miss scoped lookups; update the normalizeStreamRequestType
function to treat schemas.ImageEditStreamRequest the same as
schemas.ImageEditRequest/schemas.ImageVariationRequest by mapping it to the
"image_generation" base type (same behavior as normalizeRequestType) so
image-edit stream requests resolve pricing/overrides correctly.

In `@transports/bifrost-http/handlers/providers.go`:
- Line 438: Update the stale comment that currently says "Provider-level pricing
overrides are deprecated and ignored" to reflect the actual behavior:
provider-level pricing overrides are rejected with a 400 Bad Request. Locate the
comment near the providers.go handlers (the comment at ~line 438) and change its
wording to state that provider-level overrides are disallowed and will result in
a 400 Bad Request, matching the validation logic that returns 400 (the rejection
code around lines 323-326).

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 264-285: Add data-testid attributes to all interactive controls in
pricingOverrideDrawer.tsx: for each mapped input inside the fields loop (the
Input component bound to form.pricingValues[field.key]) add a predictable
data-testid that includes the field.key (e.g., pricing-input-<field.key>);
likewise add data-testid attributes to any selects, checkboxes, and action
buttons in this component (referencing their component names or handler
functions such as setForm, save/close handlers) so e2e can target them. Also
scan the rest of the file (areas around the other interactive controls
referenced in the review, ~lines 545-851) and add similar testids
(workspace-selector, pricing-select-<id>, pricing-checkbox-<id>, save-button,
cancel-button) following the same naming pattern.
- Around line 507-523: When editing an override the PATCH currently omits
request_types when the user selects "All request types" because
form.requestTypes is turned into undefined, preventing the backend from clearing
the existing RequestTypes; update the payload construction in the
editingOverride branch (where PatchPricingOverrideRequest is built) to set
request_types explicitly to form.requestTypes (or to an empty array when
form.requestTypes is empty) instead of undefined so the JSON contains [] when
the user clears request types; ensure the same logic around
requestPayload.request_types / form.requestTypes is used when assigning to
PatchPricingOverrideRequest so the backend conditional (if req.RequestTypes !=
nil) receives an explicit empty array.

In `@ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx`:
- Line 199: The Create Override Button (onClick={openCreateDrawer}) and all
other interactive controls in scopedPricingOverridesView (create, edit, delete
buttons and any dialog/drawer action buttons such as save/cancel in the
create/edit drawer and confirm/delete dialog) must include data-testid
attributes following the workspace convention; add descriptive, convention-based
ids (e.g. scoped-pricing-overrides-create-btn,
scoped-pricing-overrides-edit-btn-<id>,
scoped-pricing-overrides-delete-btn-<id>,
scoped-pricing-overrides-drawer-save-btn,
scoped-pricing-overrides-drawer-cancel-btn,
scoped-pricing-overrides-confirm-delete-btn) to the Button component that calls
openCreateDrawer and to the corresponding edit/delete action components and
dialog action buttons so e2e tests can reliably target them.

---

Outside diff comments:
In `@transports/bifrost-http/handlers/governance.go`:
- Around line 52-63: NewGovernanceHandler currently allows a nil modelCatalog
which can cause runtime panics; update the constructor (NewGovernanceHandler) to
validate modelCatalog and return an error if it's nil (similar to existing
checks for manager and configStore) so GovernanceHandler is never created with a
nil modelCatalog. Ensure the error message is descriptive (e.g., "model catalog
is required") and keep the returned type and structure unchanged.

---

Nitpick comments:
In `@transports/bifrost-http/handlers/providers.go`:
- Line 187: The rejection message "pricing_overrides is not a supported provider
field; use /api/governance/pricing-overrides" is duplicated; introduce a shared
constant (e.g., ErrPricingOverridesNotSupported) in
transports/bifrost-http/handlers/providers.go and replace the literal strings in
both handler functions that call SendError (the occurrences around the existing
callers at the previously noted spots) with that constant to avoid drift.

In `@transports/bifrost-http/lib/config_test.go`:
- Around line 840-858: The MockConfigStore's pricing override methods
(GetPricingOverrides, GetPricingOverrideByID, CreatePricingOverride,
UpdatePricingOverride, DeletePricingOverride) are currently no-ops; modify
MockConfigStore to hold an in-memory, concurrency-safe map (e.g.,
map[string]tables.TablePricingOverride) and a sync.Mutex or RWMutex, then
implement CreatePricingOverride to insert a copy into the map (generate or use
override.ID), GetPricingOverrides to return the slice of values,
GetPricingOverrideByID to return the entry or configstore.ErrNotFound,
UpdatePricingOverride to replace an existing entry (return ErrNotFound if
missing), and DeletePricingOverride to remove the key (return ErrNotFound if
missing); ensure methods return copies (not pointers into the map) and ignore
the variadic tx parameter.

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 21-31: The imports using the "@/lib/..." alias (RequestTypeLabels,
ModelProvider, CreatePricingOverrideRequest, PatchPricingOverrideRequest,
PricingOverride, PricingOverrideMatchType, PricingOverridePatch,
PricingOverrideScopeKind, and cn) must be changed to use relative imports from
the ui/lib directory per the UI import rule; locate the import block in
pricingOverrideDrawer.tsx and replace each "@/lib/..." import with the
corresponding relative path into ui/lib (e.g., import RequestTypeLabels from the
appropriate relative ui/lib/constants/logs, ModelProvider from
ui/lib/types/config, the governance types from ui/lib/types/governance, and cn
from ui/lib/utils) so the file no longer uses the "@/lib" alias.

In `@ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx`:
- Around line 22-23: The imports in scopedPricingOverridesView.tsx use alias
paths "@/lib/store" and "@/lib/types/governance" but should use relative ui/lib
imports per repo convention; update the import statements that bring in store
utilities (from "@/lib/store") and the types PricingOverride and
PricingOverrideScopeKind (from "@/lib/types/governance") to use relative paths
into the ui/lib directory (matching other ui/* files) so the file imports store
utilities and those types via the ui/lib relative modules instead of the "@/..."
aliases.

In `@ui/components/sidebar.tsx`:
- Around line 195-198: The route matcher special-casing
"/workspace/custom-pricing" is duplicated; extract a single shared helper (e.g.,
export function isWorkspaceRouteMatch(url: string, pathname: string)) and
replace local isRouteMatch implementations with calls to that helper; update
usages that reference the literal "/workspace/custom-pricing" and the local
pathname variable (including the other duplicated locations around the second
occurrence) so all route checks use the shared function to avoid drift.

In `@ui/lib/types/governance.ts`:
- Around line 391-419: Tighten the request_types fields by replacing the broad
string[] with a constrained union type (e.g., type RequestType = "chat" |
"completion" | "embeddings" | ...) and use RequestType[] for both
CreatePricingOverrideRequest.request_types and
PatchPricingOverrideRequest.request_types; add or reuse an existing RequestType
union/enum in this module and update any code that constructs or validates these
requests to use the new type so invalid values are caught at compile time (refer
to the CreatePricingOverrideRequest and PatchPricingOverrideRequest interfaces
and the request_types property).

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9681d2b and af1c2a1.

📒 Files selected for processing (39)
  • core/schemas/pricing_overrides.go
  • core/schemas/provider.go
  • framework/configstore/clientconfig.go
  • framework/configstore/migrations.go
  • framework/configstore/rdb.go
  • framework/configstore/store.go
  • framework/configstore/tables/modelpricing.go
  • framework/configstore/tables/pricingoverride.go
  • framework/configstore/tables/provider.go
  • framework/modelcatalog/main.go
  • framework/modelcatalog/main_test.go
  • framework/modelcatalog/overrides.go
  • framework/modelcatalog/overrides_test.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/pricing_test.go
  • framework/modelcatalog/utils.go
  • plugins/governance/main.go
  • plugins/logging/main.go
  • plugins/logging/operations.go
  • plugins/telemetry/main.go
  • transports/bifrost-http/handlers/governance.go
  • transports/bifrost-http/handlers/governance_pricing_overrides.go
  • transports/bifrost-http/handlers/providers.go
  • transports/bifrost-http/lib/config.go
  • transports/bifrost-http/lib/config_test.go
  • transports/bifrost-http/server/server.go
  • transports/config.schema.json
  • ui/app/workspace/custom-pricing/overrides/page.tsx
  • ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx
  • ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx
  • ui/app/workspace/providers/fragments/index.ts
  • ui/app/workspace/providers/fragments/pricingOverridesFormFragment.tsx
  • ui/app/workspace/virtual-keys/views/virtualKeysTable.tsx
  • ui/components/sidebar.tsx
  • ui/lib/store/apis/baseApi.ts
  • ui/lib/store/apis/governanceApi.ts
  • ui/lib/types/config.ts
  • ui/lib/types/governance.ts
  • ui/lib/types/schemas.ts
💤 Files with no reviewable changes (7)
  • ui/lib/types/schemas.ts
  • ui/app/workspace/providers/fragments/index.ts
  • framework/configstore/clientconfig.go
  • transports/config.schema.json
  • ui/lib/types/config.ts
  • ui/app/workspace/providers/fragments/pricingOverridesFormFragment.tsx
  • transports/bifrost-http/lib/config.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

Caution

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

⚠️ Outside diff range comments (2)
framework/modelcatalog/pricing.go (2)

101-127: ⚠️ Potential issue | 🟠 Major

Image-edit and image-variation requests can be priced as zero.

calculateBaseCost resolves pricing for these request types (via normalization), but the final switch routes only ImageGenerationRequest. ImageEditRequest and ImageVariationRequest currently fall through to default and return 0.

💡 Proposed fix
 	switch requestType {
 	case schemas.ChatCompletionRequest, schemas.TextCompletionRequest, schemas.ResponsesRequest:
 		return computeTextCost(pricing, input.usage)
 	case schemas.EmbeddingRequest:
 		return computeEmbeddingCost(pricing, input.usage)
 	case schemas.RerankRequest:
 		return computeRerankCost(pricing, input.usage)
 	case schemas.SpeechRequest:
 		return computeSpeechCost(pricing, input.usage, input.audioSeconds)
 	case schemas.TranscriptionRequest:
 		return computeTranscriptionCost(pricing, input.usage, input.audioSeconds, input.audioTokenDetails)
-	case schemas.ImageGenerationRequest:
+	case schemas.ImageGenerationRequest, schemas.ImageEditRequest, schemas.ImageVariationRequest:
 		return computeImageCost(pricing, input.imageUsage)
 	case schemas.VideoGenerationRequest:
 		return computeVideoCost(pricing, input.usage, input.videoSeconds)
 	default:
 		return 0
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/modelcatalog/pricing.go` around lines 101 - 127, The switch in
calculateBaseCost (after normalizeStreamRequestType and resolvePricing) only
handles schemas.ImageGenerationRequest so ImageEditRequest and
ImageVariationRequest fall through to default and get zero cost; update the
switch in calculateBaseCost to include cases for schemas.ImageEditRequest and
schemas.ImageVariationRequest and route them to computeImageCost(pricing,
input.imageUsage) (same as schemas.ImageGenerationRequest) so edited/variation
image requests use the resolved image pricing.

431-457: ⚠️ Potential issue | 🟠 Major

Image-token-specific rates are not applied in image token billing.

computeImageCost currently charges image tokens using generic token rates. This bypasses the newly introduced InputCostPerImageToken/OutputCostPerImageToken fields and can misprice image workloads.

💡 Proposed fix
-	// Text token rates (tiered)
+	// Text token rates (tiered)
 	totalTokens := imageUsage.TotalTokens
 	inputTokenRate := tieredInputRate(pricing, totalTokens)
 	outputTokenRate := tieredOutputRate(pricing, totalTokens)
 
-	inputCost := float64(inputTextTokens)*inputTokenRate + float64(inputImageTokens)*inputTokenRate
-	outputCost := float64(outputTextTokens)*outputTokenRate + float64(outputImageTokens)*outputTokenRate
+	inputImageRate := inputTokenRate
+	if pricing.InputCostPerImageToken != nil {
+		inputImageRate = *pricing.InputCostPerImageToken
+	}
+	outputImageRate := outputTokenRate
+	if pricing.OutputCostPerImageToken != nil {
+		outputImageRate = *pricing.OutputCostPerImageToken
+	}
+
+	inputCost := float64(inputTextTokens)*inputTokenRate + float64(inputImageTokens)*inputImageRate
+	outputCost := float64(outputTextTokens)*outputTokenRate + float64(outputImageTokens)*outputImageRate
 
 	return inputCost + outputCost
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@framework/modelcatalog/pricing.go` around lines 431 - 457, The
computeImageCost code is charging image tokens using generic tiered rates (via
tieredInputRate/tieredOutputRate) instead of the image-specific rates; change it
so text tokens still use tieredInputRate/tieredOutputRate but image tokens use
the pricing fields InputCostPerImageToken and OutputCostPerImageToken (e.g., set
inputImageRate := pricing.InputCostPerImageToken and outputImageRate :=
pricing.OutputCostPerImageToken and use those to compute
float64(inputImageTokens)*inputImageRate and
float64(outputImageTokens)*outputImageRate), leaving the rest of the logic
(totalTokens, text token handling, and function names
tieredInputRate/tieredOutputRate) unchanged.
🧹 Nitpick comments (1)
ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx (1)

31-43: Extract scope-kind parsing/resolution into a shared utility.

parseScopeKind/resolveScopeKind logic is duplicated across this file and ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx. As new scope kinds evolve in this stacked change, this can drift and silently break filtering vs. form behavior.

♻️ Suggested refactor
+// ui/app/workspace/custom-pricing/overrides/scopeKind.ts
+import { PricingOverride, PricingOverrideScopeKind } from "@/lib/types/governance";
+
+export type ScopeFilter = "all" | PricingOverrideScopeKind;
+
+export function parseScopeKind(value: string | null): ScopeFilter {
+  if (
+    value === "global" ||
+    value === "provider" ||
+    value === "provider_key" ||
+    value === "virtual_key" ||
+    value === "virtual_key_provider" ||
+    value === "virtual_key_provider_key"
+  ) {
+    return value;
+  }
+  return "all";
+}
+
+export function resolveScopeKind(override: PricingOverride): PricingOverrideScopeKind {
+  if (
+    override.scope_kind === "global" ||
+    override.scope_kind === "provider" ||
+    override.scope_kind === "provider_key" ||
+    override.scope_kind === "virtual_key" ||
+    override.scope_kind === "virtual_key_provider" ||
+    override.scope_kind === "virtual_key_provider_key"
+  ) {
+    return override.scope_kind;
+  }
+  if (override.virtual_key_id) {
+    if (override.provider_key_id) return "virtual_key_provider_key";
+    if (override.provider_id) return "virtual_key_provider";
+    return "virtual_key";
+  }
+  if (override.provider_key_id) return "provider_key";
+  if (override.provider_id) return "provider";
+  return "global";
+}

Also applies to: 77-96

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx`
around lines 31 - 43, Duplicate scope-kind validation logic (parseScopeKind /
resolveScopeKind) exists in scopedPricingOverridesView.tsx and
pricingOverrideDrawer.tsx; extract this into a single shared utility function
(e.g., parseScopeKind or resolveScopeKind) in a common module and replace both
local implementations with an import and call to that utility. Specifically,
move the allowed scope list and the normalization logic (returning the validated
ScopeFilter or "all") into the new utility, update
scopedPricingOverridesView.tsx to call parseScopeKind(value) instead of its
inline function, and update pricingOverrideDrawer.tsx to use the same exported
function so both files share one source of truth for scope-kind resolution.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 699-704: The button label can display "undefined (n)" when
form.requestTypes[0] isn't in REQUEST_TYPE_GROUPS; update the rendering logic
that uses getRequestTypeGroup(form.requestTypes[0]) (used in the
pricing-override-request-types-btn) to guard against unknown keys and provide a
fallback string (e.g. "Unknown request type" or "Other") when
getRequestTypeGroup returns undefined or the value isn't found in
REQUEST_TYPE_GROUPS; implement the guard inline where the label is computed so
the fallback is shown instead of undefined.
- Around line 338-363: The effect in useEffect that builds scopedForm from
scopeLock over-applies the lock by overwriting all ID fields and forcing a
scopeRoot, which disables Save when scopeLock is partial; change the logic in
the useEffect (the block that creates scopedForm) to only override the specific
fields present on scopeLock (virtualKeyID, providerID, providerKeyID) and leave
other fields from defaultFormState intact, and compute scopeRoot only when
scopeLock.scopeKind clearly implies a single root (otherwise keep
defaultFormState.scopeRoot); update references to FormState, setForm,
defaultFormState, scopeLock, virtualKeyID, providerID, providerKeyID, and
scopeRoot so incomplete scopeLock values do not hide/require selectors or break
validation.

---

Outside diff comments:
In `@framework/modelcatalog/pricing.go`:
- Around line 101-127: The switch in calculateBaseCost (after
normalizeStreamRequestType and resolvePricing) only handles
schemas.ImageGenerationRequest so ImageEditRequest and ImageVariationRequest
fall through to default and get zero cost; update the switch in
calculateBaseCost to include cases for schemas.ImageEditRequest and
schemas.ImageVariationRequest and route them to computeImageCost(pricing,
input.imageUsage) (same as schemas.ImageGenerationRequest) so edited/variation
image requests use the resolved image pricing.
- Around line 431-457: The computeImageCost code is charging image tokens using
generic tiered rates (via tieredInputRate/tieredOutputRate) instead of the
image-specific rates; change it so text tokens still use
tieredInputRate/tieredOutputRate but image tokens use the pricing fields
InputCostPerImageToken and OutputCostPerImageToken (e.g., set inputImageRate :=
pricing.InputCostPerImageToken and outputImageRate :=
pricing.OutputCostPerImageToken and use those to compute
float64(inputImageTokens)*inputImageRate and
float64(outputImageTokens)*outputImageRate), leaving the rest of the logic
(totalTokens, text token handling, and function names
tieredInputRate/tieredOutputRate) unchanged.

---

Nitpick comments:
In `@ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx`:
- Around line 31-43: Duplicate scope-kind validation logic (parseScopeKind /
resolveScopeKind) exists in scopedPricingOverridesView.tsx and
pricingOverrideDrawer.tsx; extract this into a single shared utility function
(e.g., parseScopeKind or resolveScopeKind) in a common module and replace both
local implementations with an import and call to that utility. Specifically,
move the allowed scope list and the normalization logic (returning the validated
ScopeFilter or "all") into the new utility, update
scopedPricingOverridesView.tsx to call parseScopeKind(value) instead of its
inline function, and update pricingOverrideDrawer.tsx to use the same exported
function so both files share one source of truth for scope-kind resolution.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between af1c2a1 and fff3519.

📒 Files selected for processing (6)
  • framework/modelcatalog/main.go
  • framework/modelcatalog/overrides.go
  • framework/modelcatalog/pricing.go
  • framework/modelcatalog/utils.go
  • ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx
  • ui/app/workspace/custom-pricing/overrides/scopedPricingOverridesView.tsx

@coderabbitai coderabbitai bot requested a review from akshaydeo February 27, 2026 21:07
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.

♻️ Duplicate comments (1)
ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx (1)

787-852: ⚠️ Potential issue | 🟠 Major

Add data-testid to remaining interactive controls (accordion toggles + JSON editor surface).

Line 789/802/815/828/841 AccordionTrigger elements are interactive but have no test IDs, and the JSON editor interaction surface at Line 860 lacks a stable selector. This still breaks the workspace e2e selector contract.

Proposed update
 							<AccordionItem value="token">
-								<AccordionTrigger>
+								<AccordionTrigger data-testid="pricing-override-token-accordion-trigger">
@@
 							<AccordionItem value="cache">
-								<AccordionTrigger>
+								<AccordionTrigger data-testid="pricing-override-cache-accordion-trigger">
@@
 							<AccordionItem value="image">
-								<AccordionTrigger>
+								<AccordionTrigger data-testid="pricing-override-image-accordion-trigger">
@@
 							<AccordionItem value="audio-video">
-								<AccordionTrigger>
+								<AccordionTrigger data-testid="pricing-override-audio-video-accordion-trigger">
@@
 							<AccordionItem value="other">
-								<AccordionTrigger>
+								<AccordionTrigger data-testid="pricing-override-other-accordion-trigger">
@@
-						<div className={cn("bg-muted/50 overflow-hidden rounded-md border", jsonError && "border-destructive")}>
+						<div
+							data-testid="pricing-override-json-editor-container"
+							className={cn("bg-muted/50 overflow-hidden rounded-md border", jsonError && "border-destructive")}
+						>
As per coding guidelines `ui/app/workspace/**/*.tsx`: “must include data-testid attributes on all interactive elements.”

Also applies to: 860-869

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx` around
lines 787 - 852, Add stable data-testid attributes to all interactive
AccordionTrigger components and to the JSON editor interaction surface so e2e
tests can reliably select them: update each AccordionTrigger in this file (the
triggers for values "token", "cache", "image", "audio-video", and "other") to
include a data-testid like data-testid="accordion-trigger-{value}" and update
the JSON editor wrapper rendered by renderFields (or the specific JSON editor
element used around lines ~860–869) to include a stable data-testid such as
data-testid="json-editor-surface"; ensure attribute names are unique and follow
the existing testid naming convention.
🧹 Nitpick comments (1)
ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx (1)

15-31: Switch ui/lib imports to relative paths in this file.

Imports for store/constants/types/utils are using @/lib/* alias, but this file is under ui/ and should use relative imports from ui/lib per the provided repo guideline.

As per coding guidelines ui/**/*.{ts,tsx}: “Use relative imports from the ui/lib directory for constants, utilities, and types.”

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx` around
lines 15 - 31, The imports at the top of pricingOverrideDrawer.tsx that use the
"@/lib/*" alias (e.g., getErrorMessage, useCreatePricingOverrideMutation,
useGetProvidersQuery, RequestTypeLabels, ModelProvider,
CreatePricingOverrideRequest, cn) must be changed to relative imports that point
into the ui/lib directory; locate the import block that lists these symbols and
replace each "@/lib/..." import with the corresponding relative path import from
ui/lib (maintain the same exported symbol names and update import paths only) so
the file follows the ui/**/*.{ts,tsx} guideline to use relative ui/lib imports.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 787-852: Add stable data-testid attributes to all interactive
AccordionTrigger components and to the JSON editor interaction surface so e2e
tests can reliably select them: update each AccordionTrigger in this file (the
triggers for values "token", "cache", "image", "audio-video", and "other") to
include a data-testid like data-testid="accordion-trigger-{value}" and update
the JSON editor wrapper rendered by renderFields (or the specific JSON editor
element used around lines ~860–869) to include a stable data-testid such as
data-testid="json-editor-surface"; ensure attribute names are unique and follow
the existing testid naming convention.

---

Nitpick comments:
In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 15-31: The imports at the top of pricingOverrideDrawer.tsx that
use the "@/lib/*" alias (e.g., getErrorMessage,
useCreatePricingOverrideMutation, useGetProvidersQuery, RequestTypeLabels,
ModelProvider, CreatePricingOverrideRequest, cn) must be changed to relative
imports that point into the ui/lib directory; locate the import block that lists
these symbols and replace each "@/lib/..." import with the corresponding
relative path import from ui/lib (maintain the same exported symbol names and
update import paths only) so the file follows the ui/**/*.{ts,tsx} guideline to
use relative ui/lib imports.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between fff3519 and 0a0e092.

📒 Files selected for processing (1)
  • ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx

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

🧹 Nitpick comments (1)
ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx (1)

871-880: Consider adding data-testid to the JSON CodeEditor wrapper for e2e testability.

The JSON patch editor lacks a data-testid, which may complicate e2e tests that need to interact with or verify its content. Consider adding a data-testid to the wrapper div or the CodeEditor component if it supports it.

♻️ Suggested improvement
-						<div className={cn("bg-muted/50 overflow-hidden rounded-md border", jsonError && "border-destructive")}>
+						<div data-testid="pricing-override-json-editor" className={cn("bg-muted/50 overflow-hidden rounded-md border", jsonError && "border-destructive")}>
							<CodeEditor

As per coding guidelines ui/app/workspace/**/*.tsx: "must include data-testid attributes on all interactive elements."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx` around
lines 871 - 880, The CodeEditor instance rendering the JSON patch (CodeEditor
with props lang="json", code={jsonPatch}, onChange={handleJSONChange}) is
missing a data-testid which breaks e2e selectors; add a data-testid attribute
(e.g., data-testid="json-patch-editor" or similar) to the outer wrapper element
or directly to the CodeEditor component if it supports passthrough props so
tests can reliably query the editor and its contents.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 546-559: The payload is converting optional IDs to empty strings
which relies on backend normalization; instead construct
PatchPricingOverrideRequest so virtual_key_id, provider_id and provider_key_id
are omitted when undefined (or explicitly set to null if you prefer null
semantics) rather than using "" — update the block that builds payload (used
before calling patchOverride({ id: editingOverride.id, data: payload
}).unwrap()) to only assign those keys when requestPayload.virtual_key_id /
provider_id / provider_key_id are defined (or assign null if you intentionally
want null), leaving the properties off otherwise so the JSON matches the
TypeScript optional semantics and backend omitempty handling.

---

Nitpick comments:
In `@ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx`:
- Around line 871-880: The CodeEditor instance rendering the JSON patch
(CodeEditor with props lang="json", code={jsonPatch},
onChange={handleJSONChange}) is missing a data-testid which breaks e2e
selectors; add a data-testid attribute (e.g., data-testid="json-patch-editor" or
similar) to the outer wrapper element or directly to the CodeEditor component if
it supports passthrough props so tests can reliably query the editor and its
contents.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0a0e092 and ec78e61.

📒 Files selected for processing (1)
  • ui/app/workspace/custom-pricing/overrides/pricingOverrideDrawer.tsx

@Pratham-Mishra04 Pratham-Mishra04 changed the base branch from 02-26-refactor_pricing_module_refactor to graphite-base/1825 March 3, 2026 05:21
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.

1 participant