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:
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user