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,
|
Wrench,
|
||||||
} from "lucide-react-native";
|
} from "lucide-react-native";
|
||||||
import type { ToolCallDetail, ToolCallIconName } from "@server/server/agent/agent-sdk-types";
|
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 }>;
|
export type ToolCallIconComponent = ComponentType<{ size?: number; color?: string }>;
|
||||||
|
|
||||||
@@ -57,6 +59,9 @@ export function resolveToolCallIcon(
|
|||||||
if (lowerName === "speak") {
|
if (lowerName === "speak") {
|
||||||
return MicVocal;
|
return MicVocal;
|
||||||
}
|
}
|
||||||
|
if (isPaseoToolName(lowerName)) {
|
||||||
|
return PaseoLogo;
|
||||||
|
}
|
||||||
if (lowerName === "task") {
|
if (lowerName === "task") {
|
||||||
return Bot;
|
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;
|
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 {
|
export function isLikelyExternalToolName(name: string): boolean {
|
||||||
const normalized = normalizeToolName(name);
|
const normalized = normalizeToolName(name);
|
||||||
if (!normalized) {
|
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", () => {
|
it("labels plan detail rows as Plan", () => {
|
||||||
const display = buildToolCallDisplayModel({
|
const display = buildToolCallDisplayModel({
|
||||||
name: "plan",
|
name: "plan",
|
||||||
|
|||||||
@@ -1,4 +1,8 @@
|
|||||||
import type { ToolCallTimelineItem } from "../server/agent/agent-sdk-types.js";
|
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";
|
import { stripCwdPrefix } from "./path-utils.js";
|
||||||
|
|
||||||
export type ToolCallDisplayInput = Pick<
|
export type ToolCallDisplayInput = Pick<
|
||||||
@@ -32,6 +36,12 @@ function humanizeToolName(name: string): string {
|
|||||||
if (!trimmed) {
|
if (!trimmed) {
|
||||||
return name;
|
return name;
|
||||||
}
|
}
|
||||||
|
if (isPaseoToolName(trimmed)) {
|
||||||
|
const leaf = getPaseoToolLeafName(trimmed);
|
||||||
|
if (leaf) {
|
||||||
|
return humanizeToolName(leaf);
|
||||||
|
}
|
||||||
|
}
|
||||||
if (/[:./]/.test(trimmed) || trimmed.includes("__")) {
|
if (/[:./]/.test(trimmed) || trimmed.includes("__")) {
|
||||||
return trimmed;
|
return trimmed;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user