,
+) {
+ let application_data = json!({"cache_control": cache, "nested": {"cache_control": cache}});
+ let body = json!({
+ "cache_control": cache,
+ "model": "native-test",
+ "system": [{"type": "text", "text": "system", "cache_control": cache}],
+ "tools": [{"name": "tool", "cache_control": cache, "input_schema": application_data}],
+ "messages": [{"role": "user", "content": [
+ {"type": "tool_use", "input": application_data, "cache_control": cache},
+ {"type": "tool_result", "content": [{"type": "text", "text": "result", "cache_control": cache}], "cache_control": cache},
+ ]}],
+ });
+ let hint = |value: Value| {
+ let fields = value.as_object().unwrap().clone();
+ Value::Object(
+ fields
+ .into_iter()
+ .chain(
+ expected_cache
+ .clone()
+ .map(|cache| ("cache_control".into(), cache)),
+ )
+ .collect(),
+ )
+ };
+ let expected = hint(json!({
+ "model": "native-test",
+ "system": [hint(json!({"type": "text", "text": "system"}))],
+ "tools": [hint(json!({"name": "tool", "input_schema": application_data}))],
+ "messages": [{"role": "user", "content": [
+ hint(json!({"type": "tool_use", "input": application_data})),
+ hint(json!({"type": "tool_result", "content": [hint(json!({"type": "text", "text": "result"}))]})),
+ ]}],
+ }));
+ assert_eq!(portable_cache_control(body), expected);
+}
diff --git a/litellm-rust/crates/python-bridge/src/cache/AGENTS.md b/litellm-rust/crates/python-bridge/src/cache/AGENTS.md
index 3f1097f4bc2..41c82187373 100644
--- a/litellm-rust/crates/python-bridge/src/cache/AGENTS.md
+++ b/litellm-rust/crates/python-bridge/src/cache/AGENTS.md
@@ -2,6 +2,6 @@
This folder owns how Rust inference reaches the selected cache: global cache selection, route admission, delegation to a Python cache and the experimental V2 native handles. Cache algorithms, storage protocols and response-cache semantics belong to their cache crates
-`mod.rs` exposes the cache boundary to routes and module registration. Adapter directories remain private. `selection.rs` owns global cache selection, route admission and inference protocol composition for both adapters
+`mod.rs` exposes the cache boundary to routes and module registration. Adapter directories remain private. `selection.rs` asks `litellm.rust_bridge.host.cache` for the live configured cache, and owns route admission and inference protocol composition for both adapters
`python/` delegates operations to the selected Python cache without discovering configuration. `native/` owns the experimental V2 native cache handles. Neither adapter depends on shared selection or the other adapter. Shared composition depends on the adapters, and routes use only the parent module's exports
diff --git a/litellm-rust/crates/python-bridge/src/cache/selection.rs b/litellm-rust/crates/python-bridge/src/cache/selection.rs
index e6e9f8d2d4f..b2dc67fe965 100644
--- a/litellm-rust/crates/python-bridge/src/cache/selection.rs
+++ b/litellm-rust/crates/python-bridge/src/cache/selection.rs
@@ -9,6 +9,8 @@ use litellm_host::{
use pyo3::{prelude::*, types::PyDict};
use std::sync::Arc;
+const CACHE_HOST_MODULE: &str = "litellm.rust_bridge.host.cache";
+
pub(crate) struct Cached(std::marker::PhantomData
);
impl Protocol for Cached {
@@ -56,7 +58,10 @@ fn selected_cache<'py>(
kwargs: &Bound<'py, PyDict>,
call_type: &str,
) -> PyResult
diff --git a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_discount_table.test.tsx b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_discount_table.test.tsx
index 8d1ee100a2a..6713351face 100644
--- a/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_discount_table.test.tsx
+++ b/ui/litellm-dashboard/src/app/(dashboard)/cost-tracking/_components/provider_discount_table.test.tsx
@@ -295,6 +295,78 @@ describe("ProviderDiscountTable", () => {
expect(screen.getByRole("button", { name: "Cancel editing discount for OpenAI" })).toBeInTheDocument();
});
+ it("should render a Models column", () => {
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByRole("columnheader", { name: "Models" })).toBeInTheDocument();
+ });
+
+ it("should show the model pattern for a provider/pattern key and All models for a bare key", () => {
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByText("claude-*")).toBeInTheDocument();
+ expect(screen.getByText("All models")).toBeInTheDocument();
+ });
+
+ it("should split a nested pattern key on the first slash only", () => {
+ renderWithProviders(
+ ,
+ );
+ expect(screen.getByText("anthropic/claude-*")).toBeInTheDocument();
+ expect(screen.queryByText("All models")).not.toBeInTheDocument();
+ });
+
+ it("should include the pattern in the row action labels for a pattern key", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+ ,
+ );
+
+ await user.click(screen.getByRole("button", { name: /Remove discount for Vertex AI .* \(claude-\*\)/ }));
+
+ expect(onRemoveProvider).toHaveBeenCalledWith(
+ "vertex_ai/claude-*",
+ "Vertex AI (Anthropic, Gemini, etc.) (claude-*)",
+ );
+ });
+
+ it("should call onDiscountChange with the full config key when editing a pattern row", async () => {
+ const user = userEvent.setup();
+ renderWithProviders(
+ ,
+ );
+
+ await user.click(screen.getByRole("button", { name: /Edit discount for Vertex AI .* \(claude-\*\)/ }));
+ const input = screen.getByPlaceholderText("5");
+ await user.clear(input);
+ fireEvent.change(input, { target: { value: "30" } });
+ await user.click(screen.getByRole("button", { name: /Save discount for Vertex AI .* \(claude-\*\)/ }));
+
+ expect(onDiscountChange).toHaveBeenCalledWith("vertex_ai/claude-*", "0.3");
+ });
+
it("should render the empty message when no discounts are configured", () => {
renderWithProviders(
{
+ const slashIndex = key.indexOf("/");
+ if (slashIndex < 0) {
+ return { provider: key, modelPattern: null };
+ }
+ return { provider: key.slice(0, slashIndex), modelPattern: key.slice(slashIndex + 1) };
+};
+
const ProviderDiscountTable: React.FC = ({
discountConfig,
onDiscountChange,
onRemoveProvider,
}) => {
- const [editingProvider, setEditingProvider] = useState(null);
+ const [editingKey, setEditingKey] = useState(null);
const [editValue, setEditValue] = useState("");
- const handleStartEdit = (provider: string, currentDiscount: number) => {
- setEditingProvider(provider);
+ const handleStartEdit = (key: string, currentDiscount: number) => {
+ setEditingKey(key);
setEditValue((currentDiscount * 100).toString());
};
- const handleSaveEdit = (provider: string) => {
+ const handleSaveEdit = (key: string) => {
const percentValue = parseFloat(editValue);
if (!isNaN(percentValue) && percentValue >= 0 && percentValue <= 100) {
- onDiscountChange(provider, (percentValue / 100).toString());
+ onDiscountChange(key, (percentValue / 100).toString());
}
- setEditingProvider(null);
+ setEditingKey(null);
setEditValue("");
};
const handleCancelEdit = () => {
- setEditingProvider(null);
+ setEditingKey(null);
setEditValue("");
};
- const handleKeyDown = (e: React.KeyboardEvent, provider: string) => {
+ const handleKeyDown = (e: React.KeyboardEvent, key: string) => {
if (e.key === "Enter") {
- handleSaveEdit(provider);
+ handleSaveEdit(key);
} else if (e.key === "Escape") {
handleCancelEdit();
}
};
+ const rowLabel = (row: ProviderDiscountRow): string => {
+ const { displayName } = getProviderLogoAndName(row.provider);
+ return row.modelPattern ? `${displayName} (${row.modelPattern})` : displayName;
+ };
+
// Convert discount config to array and sort
const data: ProviderDiscountRow[] = Object.entries(discountConfig)
- .map(([provider, discount]) => ({ provider, discount }))
+ .map(([key, discount]) => ({ key, ...splitDiscountKey(key), discount }))
.sort((a, b) => {
const displayA = getProviderLogoAndName(a.provider).displayName;
const displayB = getProviderLogoAndName(b.provider).displayName;
- return displayA.localeCompare(displayB);
+ return displayA.localeCompare(displayB) || (a.modelPattern ?? "").localeCompare(b.modelPattern ?? "");
});
return (
@@ -78,19 +93,28 @@ const ProviderDiscountTable: React.FC = ({
);
},
},
+ {
+ header: "Models",
+ cell: (row) =>
+ row.modelPattern ? (
+ {row.modelPattern}
+ ) : (
+ All models
+ ),
+ },
{
header: "Discount Percentage",
numeric: true,
cell: (row) => {
- const { displayName } = getProviderLogoAndName(row.provider);
+ const label = rowLabel(row);
return (
- {editingProvider === row.provider ? (
+ {editingKey === row.key ? (
<>
setEditValue(e.target.value)}
- onKeyDown={(e) => handleKeyDown(e, row.provider)}
+ onKeyDown={(e) => handleKeyDown(e, row.key)}
placeholder="5"
className="w-20"
autoFocus
@@ -99,8 +123,8 @@ const ProviderDiscountTable: React.FC
= ({