diff --git a/.changeset/fn-7217-zai-glm-icons.md b/.changeset/fn-7217-zai-glm-icons.md
new file mode 100644
index 0000000000..4ef1a4938c
--- /dev/null
+++ b/.changeset/fn-7217-zai-glm-icons.md
@@ -0,0 +1,7 @@
+---
+"@runfusion/fusion": patch
+---
+
+summary: Show Z.ai icons for GLM model rows in Command Center token analytics.
+category: fix
+dev: Maps standalone glm-* model labels through the shared provider-icon inference helper.
diff --git a/packages/dashboard/app/components/command-center/__tests__/CommandCenter.test.tsx b/packages/dashboard/app/components/command-center/__tests__/CommandCenter.test.tsx
index 609431ea81..266d80dca3 100644
--- a/packages/dashboard/app/components/command-center/__tests__/CommandCenter.test.tsx
+++ b/packages/dashboard/app/components/command-center/__tests__/CommandCenter.test.tsx
@@ -106,6 +106,31 @@ function tokenFixture(totalTokens = 1_500) {
};
}
+function glmOverviewTokenFixture() {
+ const groups = [
+ makeTokenGroup("glm-5.1", 1_400),
+ makeTokenGroup("gpt-4o", 1_200),
+ makeTokenGroup("claude-sonnet", 1_000),
+ ];
+ const totals = groups.reduce(
+ (acc, group) => ({
+ inputTokens: acc.inputTokens + group.inputTokens,
+ outputTokens: acc.outputTokens + group.outputTokens,
+ cachedTokens: acc.cachedTokens + group.cachedTokens,
+ cacheWriteTokens: acc.cacheWriteTokens + group.cacheWriteTokens,
+ totalTokens: acc.totalTokens + group.totalTokens,
+ nTasks: acc.nTasks + group.nTasks,
+ }),
+ { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
+ );
+
+ return {
+ ...tokenFixture(totals.totalTokens),
+ totals,
+ groups,
+ };
+}
+
function manyModelTokenFixture() {
const groups = [
makeTokenGroup("model-01", 2_000),
@@ -639,6 +664,20 @@ describe("CommandCenter shell", () => {
}
});
+ it("renders standalone GLM model rows with the Z.ai icon in Overview token bars", async () => {
+ mockOverviewApi({ tokens: glmOverviewTokenFixture() });
+ render();
+
+ const overviewTokenChart = await screen.findByTestId("command-center-overview-chart-tokens");
+ const glmBarLabel = within(overviewTokenChart).getByText("glm-5.1").closest(".cc-bar-label");
+ expect(glmBarLabel).toBeTruthy();
+ expect(providerIconIn(overviewTokenChart, "zai")).toBeTruthy();
+ expect(providerIconIn(overviewTokenChart, "openai")).toBeTruthy();
+ expect(providerIconIn(overviewTokenChart, "anthropic")).toBeTruthy();
+ expect(screen.getByRole("img", { name: "zai glm-5.1: 1,400" })).toBeTruthy();
+ expect(providerIconIn(screen.getByTestId("cc-overview-pie"), "zai")).toBeNull();
+ });
+
it("renders Overview providerless and unknown model icons without touching pie labels", async () => {
mockOverviewApi({
tokens: {
diff --git a/packages/dashboard/app/components/command-center/areas/__tests__/TokensArea.test.tsx b/packages/dashboard/app/components/command-center/areas/__tests__/TokensArea.test.tsx
index 21486b467c..cbe814b809 100644
--- a/packages/dashboard/app/components/command-center/areas/__tests__/TokensArea.test.tsx
+++ b/packages/dashboard/app/components/command-center/areas/__tests__/TokensArea.test.tsx
@@ -83,6 +83,36 @@ function tokenFixture() {
};
}
+function glmMixedProviderTokenFixture() {
+ const groups = [
+ makeTokenGroup("glm-5.1", 2_400),
+ makeTokenGroup("glm-4.5-air", 2_200),
+ makeTokenGroup("glm-5v-turbo", 2_000),
+ makeTokenGroup("gpt-4o-mini", 1_800),
+ makeTokenGroup("claude-sonnet-4-5", 1_600),
+ makeTokenGroup("custom-model-v1", 1_400),
+ makeTokenGroup(null, 1_200),
+ ];
+ const totals = groups.reduce(
+ (acc, group) => ({
+ inputTokens: acc.inputTokens + group.inputTokens,
+ outputTokens: acc.outputTokens + group.outputTokens,
+ cachedTokens: acc.cachedTokens + group.cachedTokens,
+ cacheWriteTokens: acc.cacheWriteTokens + group.cacheWriteTokens,
+ totalTokens: acc.totalTokens + group.totalTokens,
+ nTasks: acc.nTasks + group.nTasks,
+ }),
+ { inputTokens: 0, outputTokens: 0, cachedTokens: 0, cacheWriteTokens: 0, totalTokens: 0, nTasks: 0 },
+ );
+
+ return {
+ ...tokenFixture(),
+ totals,
+ cost: { usd: null, unavailable: true, stale: false },
+ groups,
+ };
+}
+
function manyModelTokenFixture() {
const groups = [
makeTokenGroup("claude-sonnet-4-5", 2_000),
@@ -152,6 +182,29 @@ describe("TokensArea provider model icons", () => {
expect(screen.getByTestId("cc-tokens-pie")).toHaveTextContent("(unknown)");
});
+ it("renders standalone GLM model rows with Z.ai icons across bars and table", async () => {
+ apiMock.mockResolvedValue(glmMixedProviderTokenFixture());
+ render();
+
+ const table = await screen.findByTestId("cc-tokens-table");
+ const byModelChart = screen.getByRole("list", { name: "Tokens by model" });
+
+ for (const label of ["glm-5.1", "glm-4.5-air", "glm-5v-turbo"]) {
+ const row = screen.getByTestId(`cc-tokens-row-${label}`);
+ expect(within(row).getByText(label)).toBeTruthy();
+ expect(within(row).getByTestId("provider-icon-zai")).toHaveAttribute("data-provider", "zai");
+ const barLabel = within(byModelChart).getByText(label).closest(".cc-bar-label");
+ expect(barLabel?.firstElementChild).toHaveAttribute("data-testid", "provider-icon-zai");
+ expect(barLabel?.firstElementChild).toHaveAttribute("data-provider", "zai");
+ }
+
+ expect(within(screen.getByTestId("cc-tokens-row-gpt-4o-mini")).getByTestId("provider-icon-openai")).toBeTruthy();
+ expect(within(screen.getByTestId("cc-tokens-row-claude-sonnet-4-5")).getByTestId("provider-icon-anthropic")).toBeTruthy();
+ expect(within(screen.getByTestId("cc-tokens-row-custom-model-v1")).getByTestId("provider-icon-custom-model-v1")).toBeTruthy();
+ expect(within(screen.getByTestId("cc-tokens-row-unknown")).getByTestId("provider-icon-")).toBeTruthy();
+ expect(table.querySelectorAll('.provider-icon[data-provider="zai"]').length).toBe(3);
+ });
+
it("renders every analytics model group in detail bar, pie, and table even beyond the old cap", async () => {
apiMock.mockResolvedValue(manyModelTokenFixture());
render();
diff --git a/packages/dashboard/app/utils/__tests__/providerIconKey.test.ts b/packages/dashboard/app/utils/__tests__/providerIconKey.test.ts
index f39a357f33..b28691d98f 100644
--- a/packages/dashboard/app/utils/__tests__/providerIconKey.test.ts
+++ b/packages/dashboard/app/utils/__tests__/providerIconKey.test.ts
@@ -13,6 +13,9 @@ describe("inferProviderIconKey", () => {
["minimax-text-01", "minimax"],
["zhipu-glm-4", "zai"],
["zai/glm-4.5", "zai"],
+ ["glm-5.1", "zai"],
+ ["glm-4.5-air", "zai"],
+ ["glm-5v-turbo", "zai"],
["kimi-k2", "kimi"],
["moonshot-v1", "kimi"],
["amazon-bedrock-claude", "anthropic"],
@@ -27,6 +30,7 @@ describe("inferProviderIconKey", () => {
it("returns unknown ids unchanged so ProviderIcon can render its fallback", () => {
expect(inferProviderIconKey("custom-model-v1")).toBe("custom-model-v1");
+ expect(inferProviderIconKey("not-a-glmish-model")).toBe("not-a-glmish-model");
expect(inferProviderIconKey("")).toBe("");
});
});
diff --git a/packages/dashboard/app/utils/providerIconKey.ts b/packages/dashboard/app/utils/providerIconKey.ts
index 79d8d2a7ea..a1b85c46d2 100644
--- a/packages/dashboard/app/utils/providerIconKey.ts
+++ b/packages/dashboard/app/utils/providerIconKey.ts
@@ -1,7 +1,12 @@
/*
FNXC:ProviderIcons 2026-06-25-00:00:
Command Center model analytics group rows by model id only, so dashboard model-name surfaces must infer the provider icon from the model/provider text before handing off to ProviderIcon's fallback-safe renderer.
+
+FNXC:ProviderIcons 2026-06-28-20:46:
+Standalone GLM model ids such as glm-5.1, glm-4.5-air, and glm-5v-turbo are Z.ai/Zhipu-family models even when the analytics label omits zai/zhipu. Match GLM only at model-id segment boundaries so unrelated words do not hijack the Z.ai provider icon.
*/
+const GLM_MODEL_SEGMENT_PATTERN = /(?:^|[/:_-])glm(?:$|[\d._:-]|-[\da-z])/;
+
export function inferProviderIconKey(modelOrProviderName: string): string {
const normalized = modelOrProviderName.toLowerCase();
@@ -21,7 +26,7 @@ export function inferProviderIconKey(modelOrProviderName: string): string {
if (normalized.includes("minimax")) {
return "minimax";
}
- if (normalized.includes("zai") || normalized.includes("zhipu")) {
+ if (normalized.includes("zai") || normalized.includes("zhipu") || GLM_MODEL_SEGMENT_PATTERN.test(normalized)) {
return "zai";
}
if (normalized.includes("kimi") || normalized.includes("moonshot")) {