fix(ui): keep vendor names and agent instructions in AI wording under SI terminology (#48670)

XecGuard AI and Airia AI join the third-party names the SI swap leaves alone, so
their Guardrail Garden cards stop reading "XecGuard SI" and "Airia SI"

The copyable agent instructions go to Claude Code and Codex, so they keep AI the
same way the compliance prompts do
This commit is contained in:
yuneng-jiang 2026-10-27 16:26:01 -07:00 • committed by GitHub
parent 969161501a
commit cedae29cc7
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
4 changed files with 29 additions and 14 deletions

View file

@ -2,7 +2,7 @@ import { cloneElement, type MouseEventHandler, type ReactElement, type ReactNode
import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
import { render, screen, act } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { toSiTerminology } from "@/lib/terminology";
import { TerminologyProvider, useTerminology } from "@/contexts/TerminologyContext";
import CreatedKeyDisplay, { buildAgentInstructions } from "./CreatedKeyDisplay";
import { toast } from "@/lib/toast";
@ -13,6 +13,10 @@ vi.mock("@/components/networking", () => ({
getProxyBaseUrl: vi.fn(() => "http://gw.test:4000/"),
}));
vi.mock("@/app/(dashboard)/hooks/uiConfig/useUIConfig", () => ({
useUIConfig: () => ({ data: { use_si_terminology: true } }),
}));
vi.mock("react-copy-to-clipboard", () => ({
CopyToClipboard: ({
text,
@ -159,6 +163,23 @@ describe("CreatedKeyDisplay", () => {
expect(screen.getByRole("button", { name: "Copied!" })).toBeInTheDocument();
expect(toast.success).toHaveBeenCalledWith("Instructions copied");
});
it("copies agent instructions in AI wording even when the UI uses SI terminology", async () => {
const user = userEvent.setup({ advanceTimers: vi.advanceTimersByTime });
const GatewayLabel = () => <span>{useTerminology()("AI Gateway")}</span>;
render(
<TerminologyProvider>
<GatewayLabel />
<CreatedKeyDisplay apiKey="sk-test-123" />
</TerminologyProvider>,
);
expect(screen.getByText("SI Gateway")).toBeInTheDocument();
await user.click(screen.getByRole("button", { name: "Copy instructions" }));
expect(mockCopy).toHaveBeenLastCalledWith(buildAgentInstructions("http://gw.test:4000", "sk-test-123"));
expect(mockCopy.mock.calls.at(-1)?.[0]).toContain("Use this LiteLLM AI gateway for model inference.");
});
});
describe("buildAgentInstructions", () => {
@ -183,11 +204,4 @@ describe("buildAgentInstructions", () => {
].join("\n"),
);
});
it("rewrites only its own copy under SI terminology, never the gateway URL or the key", () => {
const lines = buildAgentInstructions("http://AI.example.com/", "sk-AI-1", toSiTerminology).split("\n");
expect(lines[0]).toBe("Use this LiteLLM SI gateway for model inference.");
expect(lines).toContain("Gateway URL: http://AI.example.com");
expect(lines).toContain("Virtual key: sk-AI-1");
});
});

View file

@ -1,5 +1,3 @@
import { useTerminology } from "@/contexts/TerminologyContext";
import { keepTerminology, type Term } from "@/lib/terminology";
import React, { useState } from "react";
import { CopyToClipboard } from "react-copy-to-clipboard";
import { ChevronDown, ChevronUp } from "lucide-react";
@ -13,14 +11,14 @@ interface CreatedKeyDisplayProps {
apiKey: string;
}
export const buildAgentInstructions = (baseUrl: string, apiKey: string, term: Term = keepTerminology): string => {
export const buildAgentInstructions = (baseUrl: string, apiKey: string): string => {
const normalizedBaseUrl = baseUrl.replace(/\/+$/, "");
const endpointIntroduction =
"LiteLLM is a gateway that exposes OpenAI and Anthropic compatible APIs. " +
`Send the virtual key as a Bearer token (Authorization: Bearer ${apiKey}) to:`;
return [
term("Use this LiteLLM AI gateway for model inference."),
"Use this LiteLLM AI gateway for model inference.",
"",
`Gateway URL: ${normalizedBaseUrl}`,
`Virtual key: ${apiKey}`,
@ -43,12 +41,11 @@ export const buildAgentInstructions = (baseUrl: string, apiKey: string, term: Te
* Used on the Virtual Keys page and in the Add Agent wizard.
*/
const CreatedKeyDisplay: React.FC<CreatedKeyDisplayProps> = ({ apiKey }) => {
const term = useTerminology();
const [copied, setCopied] = useState(false);
const [instructionsCopied, setInstructionsCopied] = useState(false);
const [showInstructions, setShowInstructions] = useState(false);
const baseUrl = getProxyBaseUrl().replace(/\/+$/, "");
const instructions = buildAgentInstructions(baseUrl, apiKey, term);
const instructions = buildAgentInstructions(baseUrl, apiKey);
const handleCopy = () => {
setCopied(true);

View file

@ -39,6 +39,8 @@ describe("toSiTerminology", () => {
["Cisco AI Defense Inspection API for AI apps", "Cisco AI Defense Inspection API for SI apps"],
["DeepKeep AI Firewall for AI security", "DeepKeep AI Firewall for SI security"],
["Defend AI Agentic Guardrails", "Defend AI Agentic Guardrails"],
["CyCraft XecGuard AI security gateway for AI apps", "CyCraft XecGuard AI security gateway for SI apps"],
["Airia AI security platform for responsible AI", "Airia AI security platform for responsible SI"],
])("keeps third-party names inside %j", (input, expected) => {
expect(toSiTerminology(input)).toBe(expected);
});

View file

@ -9,6 +9,8 @@ const THIRD_PARTY_NAMES: readonly string[] = [
"DeepKeep AI Firewall",
"Defend AI",
"Prisma AI Runtime Security",
"XecGuard AI",
"Airia AI",
];
const THIRD_PARTY_NAME_PATTERN = new RegExp(`(${THIRD_PARTY_NAMES.join("|")})`);