Show Paseo logo and nice names for Paseo MCP tool calls

Detect Paseo MCP tool names across provider formats (Claude's
mcp__paseo__*, Codex's paseo.*) and display humanized leaf names
with the Paseo SVG logo instead of raw namespaced strings.
This commit is contained in:
Mohamed Boudra
2026-04-11 13:02:22 +07:00
parent cff41d78ab
commit 6897a400e5
5 changed files with 144 additions and 0 deletions
+5
View File
@@ -11,6 +11,8 @@ import {
Wrench,
} from "lucide-react-native";
import type { ToolCallDetail, ToolCallIconName } from "@server/server/agent/agent-sdk-types";
import { isPaseoToolName } from "@server/server/agent/tool-name-normalization";
import { PaseoLogo } from "@/components/icons/paseo-logo";
export type ToolCallIconComponent = ComponentType<{ size?: number; color?: string }>;
@@ -57,6 +59,9 @@ export function resolveToolCallIcon(
if (lowerName === "speak") {
return MicVocal;
}
if (isPaseoToolName(lowerName)) {
return PaseoLogo;
}
if (lowerName === "task") {
return Bot;
}
@@ -0,0 +1,46 @@
import { describe, expect, it } from "vitest";
import { getPaseoToolLeafName, isPaseoToolName } from "./tool-name-normalization.js";
describe("isPaseoToolName", () => {
it("detects Claude Code format", () => {
expect(isPaseoToolName("mcp__paseo__create_agent")).toBe(true);
expect(isPaseoToolName("mcp__paseo__list_agents")).toBe(true);
});
it("detects paseo_voice variant", () => {
expect(isPaseoToolName("mcp__paseo_voice__create_agent")).toBe(true);
expect(isPaseoToolName("paseo_voice.create_agent")).toBe(true);
});
it("excludes speak tools", () => {
expect(isPaseoToolName("mcp__paseo_voice__speak")).toBe(false);
expect(isPaseoToolName("mcp__paseo__speak")).toBe(false);
expect(isPaseoToolName("paseo.speak")).toBe(false);
});
it("detects Codex dot format", () => {
expect(isPaseoToolName("paseo.create_agent")).toBe(true);
});
it("rejects non-paseo tools", () => {
expect(isPaseoToolName("Bash")).toBe(false);
expect(isPaseoToolName("Read")).toBe(false);
expect(isPaseoToolName("mcp__other_server__some_tool")).toBe(false);
});
});
describe("getPaseoToolLeafName", () => {
it("extracts leaf from Claude Code format", () => {
expect(getPaseoToolLeafName("mcp__paseo__create_agent")).toBe("create_agent");
});
it("extracts leaf from Codex format", () => {
expect(getPaseoToolLeafName("paseo.create_agent")).toBe("create_agent");
expect(getPaseoToolLeafName("paseo.list_agents")).toBe("list_agents");
});
it("returns null for non-paseo tools", () => {
expect(getPaseoToolLeafName("Bash")).toBeNull();
});
});
@@ -39,6 +39,49 @@ export function isLikelyNamespacedToolName(name: string): boolean {
return false;
}
export function isPaseoToolName(name: string): boolean {
const normalized = normalizeToolName(name);
if (isSpeakToolName(normalized)) {
return false;
}
if (normalized.includes("__")) {
const segments = normalized.split("__").filter((s) => s.length > 0);
return (
segments.length >= 3 &&
segments[0] === "mcp" &&
(segments[1] === "paseo" || segments[1]!.startsWith("paseo_"))
);
}
if (normalized.includes(".")) {
const firstSegment = normalized.split(".")[0]!;
return firstSegment === "paseo" || firstSegment.startsWith("paseo_");
}
return false;
}
export function getPaseoToolLeafName(name: string): string | null {
const normalized = normalizeToolName(name);
if (normalized.includes("__")) {
const segments = normalized.split("__").filter((s) => s.length > 0);
if (
segments.length >= 3 &&
segments[0] === "mcp" &&
(segments[1] === "paseo" || segments[1]!.startsWith("paseo_"))
) {
return segments.slice(2).join("__");
}
return null;
}
if (normalized.includes(".")) {
const firstSegment = normalized.split(".")[0]!;
if (firstSegment === "paseo" || firstSegment.startsWith("paseo_")) {
return normalized.split(".").slice(1).join(".");
}
return null;
}
return null;
}
export function isLikelyExternalToolName(name: string): boolean {
const normalized = normalizeToolName(name);
if (!normalized) {
@@ -141,6 +141,46 @@ describe("shared tool-call display mapping", () => {
});
});
it("humanizes Paseo MCP tool names (Claude Code format)", () => {
const display = buildToolCallDisplayModel({
name: "mcp__paseo__create_agent",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Create Agent");
});
it("humanizes Paseo MCP tool names (Codex format)", () => {
const display = buildToolCallDisplayModel({
name: "paseo.create_agent",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Create Agent");
});
it("humanizes list_agents Paseo tool", () => {
const display = buildToolCallDisplayModel({
name: "mcp__paseo__list_agents",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("List Agents");
});
it("does not override speak tool display name", () => {
const display = buildToolCallDisplayModel({
name: "speak",
status: "running",
error: null,
detail: { type: "unknown", input: null, output: null },
});
expect(display.displayName).toBe("Speak");
});
it("labels plan detail rows as Plan", () => {
const display = buildToolCallDisplayModel({
name: "plan",
@@ -1,4 +1,8 @@
import type { ToolCallTimelineItem } from "../server/agent/agent-sdk-types.js";
import {
getPaseoToolLeafName,
isPaseoToolName,
} from "../server/agent/tool-name-normalization.js";
import { stripCwdPrefix } from "./path-utils.js";
export type ToolCallDisplayInput = Pick<
@@ -32,6 +36,12 @@ function humanizeToolName(name: string): string {
if (!trimmed) {
return name;
}
if (isPaseoToolName(trimmed)) {
const leaf = getPaseoToolLeafName(trimmed);
if (leaf) {
return humanizeToolName(leaf);
}
}
if (/[:./]/.test(trimmed) || trimmed.includes("__")) {
return trimmed;
}