Files
paseo/packages/app/src/tool-calls/presentation.ts
T
Mohamed Boudra d0bc188a5b Show agent messages and completion notifications with sender identity (#6373)
* feat(agents): show agent messages and completion notifications with sender identity

* fix(agents): preserve human envelope examples and attachment context

* fix(agents): separate message provenance from timeline presentation
2026-10-08 14:42:34 +01:00

100 lines
3.4 KiB
TypeScript

import type { AgentMessage } from "@getpaseo/protocol/agent-message";
import { buildAgentMessageDisplay } from "@getpaseo/protocol/agent-message-display";
import type { PlanOutcome } from "@/components/plan-card";
import type { ComponentType } from "react";
import type { ToolCallDetail } from "@getpaseo/protocol/agent-types";
import type { ToolCallDisplayInput } from "@/utils/tool-call-display";
import { buildToolCallDisplayModel } from "@/utils/tool-call-display";
import { extractToolCallFilePath } from "@/utils/extract-tool-call-file-path";
import {
hasMeaningfulToolCallDetail,
isPendingToolCallDetail,
} from "@/utils/tool-call-detail-state";
type ToolCallStatus = "executing" | "running" | "completed" | "failed" | "canceled";
export type ToolCallPresentationIcon = ComponentType<{ size?: number; color?: string }>;
interface BuildToolCallPresentationInput {
agentMessage?: AgentMessage;
toolName: string;
status: ToolCallStatus;
error: unknown;
detail?: ToolCallDetail;
cwd?: string;
metadata?: Record<string, unknown>;
resolveIcon: ToolCallIconResolver;
}
export interface ToolCallPresentation {
detail: ToolCallDetail | undefined;
displayName: string;
summary?: string;
errorText?: string;
icon: ToolCallPresentationIcon;
isLoadingDetails: boolean;
hasDetails: boolean;
canOpenDetails: boolean;
openFilePath: string | null;
isPlan: boolean;
planOutcome?: PlanOutcome;
}
export type ToolCallIconResolver = (
toolName: string,
detail: ToolCallDetail | undefined,
) => ToolCallPresentationIcon;
function displayStatus(status: ToolCallStatus): ToolCallDisplayInput["status"] {
return status === "executing" ? "running" : status;
}
function displayDetail(detail: ToolCallDetail | undefined): ToolCallDetail {
return detail ?? { type: "unknown", input: null, output: null };
}
export function buildToolCallPresentation(
input: BuildToolCallPresentationInput,
): ToolCallPresentation {
const detail = input.agentMessage
? buildAgentMessageDisplay(input.agentMessage).detail
: input.detail;
const detailForDisplay = displayDetail(detail);
const displayModel = buildToolCallDisplayModel({
name: input.toolName,
agentMessage: input.agentMessage,
status: displayStatus(input.status),
error: input.error ?? null,
detail: detailForDisplay,
metadata: input.metadata,
cwd: input.cwd,
});
const isLoadingDetails = isPendingToolCallDetail({
detail,
status: input.status,
error: input.error,
});
const hasDetails = Boolean(input.error) || hasMeaningfulToolCallDetail(detail);
return {
detail,
displayName: displayModel.displayName,
summary: displayModel.summary,
errorText: displayModel.errorText,
icon: input.resolveIcon(input.toolName, detail),
isLoadingDetails,
hasDetails,
canOpenDetails: hasDetails || isLoadingDetails,
openFilePath: extractToolCallFilePath(detail),
isPlan: input.detail?.type === "plan",
planOutcome: input.detail?.type === "plan" ? resolvePlanOutcome(input) : undefined,
};
}
function resolvePlanOutcome(input: BuildToolCallPresentationInput): PlanOutcome | undefined {
if (input.status === "canceled") return "canceled";
if (input.metadata?.approved === false) return "rejected";
if (input.metadata?.approved === true) return "approved";
if (input.status === "running" || input.status === "executing") return "pending";
return undefined;
}