Publish Claude Code Binaries / Stage and publish binaries (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-arm64 (push) Canceled after 0s
Publish Tutti App Runtime / Build darwin-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build linux-amd64 (push) Canceled after 0s
Publish Tutti App Runtime / Build windows-amd64 (push) Canceled after 0s
Sync Tutti Agent Skills / sync (push) Canceled after 0s
Windows Agent Adapters / Test Windows agent adapters (push) Canceled after 0s
Windows Daemon Adapters / Test Windows daemon adapters (push) Canceled after 0s
Publish Tutti App Runtime / Publish runtime catalog (push) Canceled after 0s
246 lines
6.5 KiB
TypeScript
246 lines
6.5 KiB
TypeScript
import type { WorkspaceAgentActivityTimelineItem } from "./workspaceAgentTimelineTypes";
|
|
import { looksLikeOpaqueToolCallIdentifier } from "./workspaceAgentToolCallIdentifiers";
|
|
import {
|
|
legacyKindToToolName,
|
|
TOOL_ACTIVITY_KIND_TRANSLATION_KEYS,
|
|
type ToolActivityKind
|
|
} from "./workspaceAgentToolCallLabels";
|
|
|
|
export function resolveCanonicalToolName(
|
|
toolName: string | null | undefined
|
|
): string | null {
|
|
const normalized = normalizeToolNameToken(toolName);
|
|
switch (normalized) {
|
|
case "imagegeneration":
|
|
case "imagegen":
|
|
case "generateimage":
|
|
case "generatingimage":
|
|
case "imagegenerate":
|
|
case "imagegenerator":
|
|
return "ImageGeneration";
|
|
case "approval":
|
|
return "Approval";
|
|
case "askuserquestion":
|
|
return "AskUserQuestion";
|
|
case "enterplanmode":
|
|
return "EnterPlanMode";
|
|
case "exitplanmode":
|
|
return "ExitPlanMode";
|
|
case "toolsearch":
|
|
return "ToolSearch";
|
|
case "skill":
|
|
return "Skill";
|
|
case "think":
|
|
return "Think";
|
|
case "bash":
|
|
return "Bash";
|
|
case "read":
|
|
return "Read";
|
|
case "write":
|
|
return "Write";
|
|
case "edit":
|
|
return "Edit";
|
|
case "glob":
|
|
return "Glob";
|
|
case "grep":
|
|
return "Grep";
|
|
case "websearch":
|
|
return "WebSearch";
|
|
case "webfetch":
|
|
return "WebFetch";
|
|
case "todowrite":
|
|
return "TodoWrite";
|
|
case "task":
|
|
case "subagent":
|
|
case "runsubagent":
|
|
case "delegatetask":
|
|
case "delegateagent":
|
|
return "Agent";
|
|
case "agent":
|
|
return "Agent";
|
|
default:
|
|
// Codex/legacy tool calls (including ones reconstructed by the
|
|
// Codex/Claude Code history import pipeline, which does not run the
|
|
// live daemon's canonicalization step) report the raw snake_case
|
|
// provider tool name verbatim, e.g. "apply_patch" for a file edit.
|
|
// Recognize those exactly as the equivalent live-session ToolActivityKind
|
|
// so imported and live tool calls resolve to the same canonical name and
|
|
// render with the same specialized (e.g. diff/file-reference) content.
|
|
return resolveLegacySnakeCaseToolName(toolName);
|
|
}
|
|
}
|
|
|
|
function resolveLegacySnakeCaseToolName(
|
|
toolName: string | null | undefined
|
|
): string | null {
|
|
const trimmed = toolName?.trim() ?? "";
|
|
if (!trimmed || !(trimmed in TOOL_ACTIVITY_KIND_TRANSLATION_KEYS)) {
|
|
return null;
|
|
}
|
|
return legacyKindToToolName(trimmed as ToolActivityKind);
|
|
}
|
|
|
|
export function resolveLiveCanonicalToolName(
|
|
item: WorkspaceAgentActivityTimelineItem,
|
|
rawPayloadToolName: string | null | undefined,
|
|
legacyACPToolName: string | null
|
|
): string | null | undefined {
|
|
const itemName = (item.name ?? "").trim();
|
|
if (
|
|
rawPayloadToolName &&
|
|
looksLikeOpaqueToolCallID(rawPayloadToolName, item)
|
|
) {
|
|
return null;
|
|
}
|
|
if (
|
|
rawPayloadToolName &&
|
|
isCanonicalMcpToolName(rawPayloadToolName, item.callType, item.payload)
|
|
) {
|
|
return rawPayloadToolName.trim();
|
|
}
|
|
if (!isLiveCanonicalToolCall(item) || !hasCanonicalACPPayload(item.payload)) {
|
|
return undefined;
|
|
}
|
|
if (rawPayloadToolName) {
|
|
if (
|
|
!shouldFallBackToLegacyACPToolName(
|
|
rawPayloadToolName,
|
|
itemName,
|
|
legacyACPToolName
|
|
)
|
|
) {
|
|
return rawPayloadToolName.trim();
|
|
}
|
|
return null;
|
|
}
|
|
const normalizedCallType = (
|
|
item.callType ??
|
|
stringPayloadValue(item.payload, "callType") ??
|
|
""
|
|
)
|
|
.trim()
|
|
.toLowerCase();
|
|
if (normalizedCallType === "tool" || normalizedCallType === "mcp") {
|
|
return null;
|
|
}
|
|
return undefined;
|
|
}
|
|
|
|
function shouldFallBackToLegacyACPToolName(
|
|
rawPayloadToolName: string,
|
|
itemName: string,
|
|
legacyACPToolName: string | null
|
|
): boolean {
|
|
const trimmed = rawPayloadToolName.trim();
|
|
if (!trimmed) {
|
|
return false;
|
|
}
|
|
if (looksLikeOpaqueToolCallIdentifier(trimmed)) {
|
|
return true;
|
|
}
|
|
if (legacyACPToolName === null) {
|
|
return false;
|
|
}
|
|
if (trimmed === itemName) {
|
|
return true;
|
|
}
|
|
return looksLikeOpaqueACPFunctionAlias(trimmed);
|
|
}
|
|
|
|
function looksLikeOpaqueACPFunctionAlias(value: string): boolean {
|
|
return /^[A-Z][a-z0-9]{9,}$/.test(value);
|
|
}
|
|
|
|
function looksLikeOpaqueToolCallID(
|
|
value: string,
|
|
item: WorkspaceAgentActivityTimelineItem
|
|
): boolean {
|
|
const trimmed = value.trim();
|
|
if (!trimmed) {
|
|
return false;
|
|
}
|
|
const payloadCallID =
|
|
stringPayloadValue(item.payload, "callId") ??
|
|
stringPayloadValue(item.payload, "callID") ??
|
|
stringPayloadValue(item.payload, "call_id");
|
|
if (trimmed === item.callId?.trim() || trimmed === payloadCallID?.trim()) {
|
|
return true;
|
|
}
|
|
const lower = trimmed.toLowerCase();
|
|
if (lower.startsWith("call_")) {
|
|
return isOpaqueIdentifierTail(trimmed.slice("call_".length));
|
|
}
|
|
if (lower.startsWith("ws_")) {
|
|
return isOpaqueIdentifierTail(trimmed.slice("ws_".length));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isOpaqueIdentifierTail(value: string): boolean {
|
|
return value.length >= 12 && /^[a-z0-9]+$/i.test(value);
|
|
}
|
|
|
|
function isCanonicalMcpToolName(
|
|
toolName: string,
|
|
callType: string | undefined,
|
|
payload: Record<string, unknown> | undefined
|
|
): boolean {
|
|
const trimmed = toolName.trim();
|
|
if (!trimmed) {
|
|
return false;
|
|
}
|
|
if (trimmed.toLowerCase().startsWith("mcp__")) {
|
|
return true;
|
|
}
|
|
const payloadCallType = stringPayloadValue(payload, "callType");
|
|
return (
|
|
(callType ?? "").trim().toLowerCase() === "mcp" ||
|
|
(payloadCallType ?? "").trim().toLowerCase() === "mcp"
|
|
);
|
|
}
|
|
|
|
function isLiveCanonicalToolCall(
|
|
item: WorkspaceAgentActivityTimelineItem
|
|
): boolean {
|
|
const itemType = item.itemType.trim().toLowerCase();
|
|
return (
|
|
itemType === "call.started" ||
|
|
itemType === "call.completed" ||
|
|
itemType === "call.errored"
|
|
);
|
|
}
|
|
|
|
function hasCanonicalACPPayload(
|
|
payload: Record<string, unknown> | undefined
|
|
): boolean {
|
|
return Boolean(
|
|
recordPayloadValue(payload, "acp") ||
|
|
stringPayloadValue(payload, "sessionUpdate")
|
|
);
|
|
}
|
|
|
|
function normalizeToolNameToken(value: string | null | undefined): string {
|
|
return (value ?? "")
|
|
.trim()
|
|
.toLowerCase()
|
|
.replace(/[_\s-]+/g, "");
|
|
}
|
|
|
|
function recordPayloadValue(
|
|
value: Record<string, unknown> | undefined,
|
|
key: string
|
|
): Record<string, unknown> | undefined {
|
|
const nested = value?.[key];
|
|
return nested && typeof nested === "object" && !Array.isArray(nested)
|
|
? (nested as Record<string, unknown>)
|
|
: undefined;
|
|
}
|
|
|
|
function stringPayloadValue(
|
|
value: Record<string, unknown> | undefined,
|
|
key: string
|
|
): string | undefined {
|
|
const nested = value?.[key];
|
|
return typeof nested === "string" ? nested : undefined;
|
|
}
|