531 lines · 19.3 KB · TypeScript
RawDownload
import {  isToolLifecycleItemType,  type AssetResource,  type ThreadId,  type ToolActivitySource,  type ToolLifecycleItemType,} from "@t3tools/contracts";import { classifyMarkdownImageSource } from "@t3tools/client-runtime/markdown-images";import { resolveMediaSource } from "@t3tools/client-runtime/media-source";import { isWorkspaceImagePreviewPath } from "@t3tools/shared/filePreview"; export function isWorktreeSetupActivity(kind: string): boolean {  return kind === "setup-script.requested" || kind === "setup-script.started";} export interface WorkLogPresentationEntry {  readonly label: string;  readonly toolTitle?: string;  readonly toolData?: unknown;  readonly tone: "thinking" | "tool" | "info" | "error";  readonly command?: string;  readonly detail?: string;  readonly viewedImagePath?: string;  readonly changedFiles?: ReadonlyArray<string>;  readonly itemType?: ToolLifecycleItemType;  readonly requestKind?: string;  readonly turnId?: string | null;  readonly toolCallId?: string;  readonly toolLifecycleStatus?: string;  readonly sourceActivityKind?: string;  readonly taskId?: string;  readonly toolSource?: ToolActivitySource;} export type ToolGroupAction =  | "read"  | "edit"  | "command"  | "browser"  | "code-search"  | "search"  | "other"  | "update"; export type ToolGroupSummaryKind =  | ToolGroupAction  | "dynamic-tool"  | "agent-tool"  | "tone-tool"  | "mixed"; export function normalizeCompactToolLabel(value: string): string {  return value.replace(/\s+(?:complete|completed)\s*$/i, "").trim();} const T3_MCP_TOOL_LABELS: Record<  string,  readonly [action: string, running: string, completed: string, detail: string]> = {  orchestrator_capabilities: ["Get", "Getting", "Got", "orchestration capabilities"],  delegate_task: ["Delegate", "Delegating", "Delegated", "a child task"],  task_status: ["Get", "Getting", "Got", "delegated task status"],  task_cancel: ["Cancel", "Canceling", "Canceled", "delegated task"],  schedule_task: ["Schedule", "Scheduling", "Scheduled", "a recurring task"],  list_scheduled_tasks: ["List", "Listing", "Listed", "scheduled tasks"],  update_scheduled_task: ["Update", "Updating", "Updated", "a scheduled task"],  delete_scheduled_task: ["Delete", "Deleting", "Deleted", "a scheduled task"],  create_threads: ["Create", "Creating", "Created", "T3 threads"],  t3_thread_start: ["Start", "Starting", "Started", "a T3 thread"],  t3_thread_list: ["List", "Listing", "Listed", "T3 threads"],  t3_thread_read: ["Read", "Reading", "Read", "a T3 thread"],  t3_thread_send: ["Send", "Sending", "Sent", "to a T3 thread"],  t3_thread_wait: ["Wait", "Waiting", "Waited", "for a T3 thread"],  t3_thread_interrupt: ["Interrupt", "Interrupting", "Interrupted", "a T3 thread"],  t3_worktree_handoff: ["Hand off", "Handing off", "Handed off", "thread to a git worktree"],  t3_worktree_status: ["Get", "Getting", "Got", "thread worktree status"],  preview_status: ["Get", "Getting", "Got", "preview browser status"],  preview_open: ["Open", "Opening", "Opened", "a page in the preview browser"],  preview_navigate: ["Navigate", "Navigating", "Navigated", "the preview browser"],  preview_snapshot: [    "Take a snapshot of",    "Taking a snapshot of",    "Took a snapshot of",    "the preview page",  ],  preview_click: ["Click", "Clicking", "Clicked", "in the preview browser"],  preview_press: ["Press", "Pressing", "Pressed", "a key in the preview browser"],  preview_type: ["Type", "Typing", "Typed", "in the preview browser"],  preview_scroll: ["Scroll", "Scrolling", "Scrolled", "the preview browser"],  preview_resize: ["Resize", "Resizing", "Resized", "the preview browser"],  preview_evaluate: ["Evaluate", "Evaluating", "Evaluated", "script in the preview browser"],  preview_wait_for: ["Wait", "Waiting", "Waited", "for the preview page"],  preview_set_appearance: ["Set", "Setting", "Set", "preview browser appearance"],  preview_recording_start: ["Start", "Starting", "Started", "recording the preview browser"],  preview_recording_stop: ["Stop", "Stopping", "Stopped", "recording the preview browser"],}; function resolveT3McpToolPresentation(value: string | undefined, status: string | undefined) {  if (!value) return null;  const name = normalizeCompactToolLabel(value).replace(    /^(?:mcp__(?:t3-code|t3_code|t3code)__|(?:t3-code|t3_code|t3code)(?:[.:/]|\s*·\s*))/i,    "",  );  if (!Object.hasOwn(T3_MCP_TOOL_LABELS, name)) return null;   const [action, running, completed, detail] = T3_MCP_TOOL_LABELS[name]!;  const verb =    status === "inProgress"      ? running      : status === "completed"        ? completed        : status === "failed"          ? `Failed to ${action.toLowerCase()}`          : status === "declined"            ? `Declined to ${action.toLowerCase()}`            : status === "stopped"              ? `Stopped ${running.toLowerCase()}`              : running;   return {    displayName: `${verb} ${detail}`,    icon: name.startsWith("preview_") ? ("browser" as const) : ("t3-code" as const),  };} /** Latest live activity stays present-tense unless the call itself failed, declined, or stopped. */export function liveActivityToolStatus(status: string | undefined, presentTense: boolean) {  if (status === "failed" || status === "declined" || status === "stopped") return status;  if (presentTense || status === "inProgress") return "inProgress";  return "completed";} /** Resolves tool identity before choosing labels or icons in either client. */export function resolveWorkEntryToolPresentation(  entry: Pick<WorkLogPresentationEntry, "label" | "toolTitle" | "toolData" | "toolLifecycleStatus">,  fallbackStatus?: "inProgress" | "completed",) {  const status = entry.toolLifecycleStatus ?? fallbackStatus;  const data = entry.toolData;  if (data !== null && typeof data === "object") {    if (      "server" in data &&      typeof data.server === "string" &&      "tool" in data &&      typeof data.tool === "string"    ) {      return resolveT3McpToolPresentation(`${data.server}.${data.tool}`, status);    }    if ("toolName" in data && typeof data.toolName === "string") {      return resolveT3McpToolPresentation(data.toolName, status);    }  }   return (    resolveT3McpToolPresentation(entry.toolTitle, status) ??    resolveT3McpToolPresentation(entry.label, status)  );} function asRecord(value: unknown): Record<string, unknown> | null {  return value !== null && typeof value === "object" && !Array.isArray(value)    ? (value as Record<string, unknown>)    : null;} function nonEmptyString(value: unknown): string | null {  return typeof value === "string" && value.trim().length > 0 ? value : null;} function commandResultContent(value: unknown): string | null {  const direct = nonEmptyString(value);  if (direct) return direct;   const directContent = Array.isArray(value) ? value : null;  const record = asRecord(value);  const content = record?.content;  const contentText = nonEmptyString(content);  if (contentText) return contentText;  const blocks = directContent ?? (Array.isArray(content) ? content : null);  if (!blocks) return null;   const chunks = blocks.flatMap((entry) => {    const text = nonEmptyString(entry) ?? nonEmptyString(asRecord(entry)?.text);    return text ? [text] : [];  });  return chunks.length > 0 ? chunks.join("\n") : null;} /** Returns provider command output before it is formatted for a work-log row. */export function extractCommandOutputText(dataValue: unknown): string | null {  const data = asRecord(dataValue);  const item = asRecord(data?.item);  const itemResult = asRecord(item?.result);  const rawOutput = asRecord(data?.rawOutput);  const outputStreams = [    nonEmptyString(rawOutput?.stdout),    nonEmptyString(rawOutput?.stderr),  ].filter((value): value is string => value !== null);  const acpContent = Array.isArray(data?.content)    ? data.content        .flatMap((entryValue) => {          const entry = asRecord(entryValue);          const content = asRecord(entry?.content);          const text = entry?.type === "content" ? nonEmptyString(content?.text) : null;          return text ? [text] : [];        })        .join("\n")    : null;   const candidates = [    item?.aggregatedOutput,    itemResult?.content,    data?.rawOutput,    rawOutput?.content,    outputStreams.length > 0 ? outputStreams.join("\n") : null,    rawOutput?.output,    acpContent,    data?.result,  ];  for (const candidate of candidates) {    const text = commandResultContent(candidate);    if (text) return text;  }  return null;} /** * Ingestion caps tool details at 180 chars and appends "...", so a long command * echo no longer equals the command it repeats. Treat a truncated prefix of the * command as the same echo. */function textRepeatsCommand(text: string, commands: ReadonlyArray<string | null>): boolean {  const truncated = text.endsWith("...")    ? text.slice(0, -3)    : text.endsWith("\u2026")      ? text.slice(0, -1)      : null;  return commands.some((candidate) => {    const command = candidate?.trim();    if (!command) return false;    if (command === text) return true;    return (      truncated !== null &&      truncated.length > 0 &&      command.length > truncated.length &&      command.startsWith(truncated)    );  });} /** * Decides whether a command row's `detail` is a synthetic echo of the command * rather than real output. OpenCode stores completed output in `detail` with no * other output channel, so plain equality is only treated as synthetic when the * payload shape shows the detail came from the command: Codex item metadata, * an ACP tool call (`data.toolCallId`, `kind: "execute"`), a Claude tool-name * prefix, or no structured command at all. */export function commandDetailRepeatsCommand(input: {  readonly detail: string;  readonly command: string | null;  readonly rawCommand: string | null;  readonly toolName: unknown;  readonly data: unknown;}): boolean {  const toolName = nonEmptyString(input.toolName)?.trim();  const detail = input.detail.trim();  const commands = [input.command, input.rawCommand];  if (toolName) {    const prefix = `${toolName}:`;    if (detail.toLowerCase().startsWith(prefix.toLowerCase())) {      const unprefixed = detail.slice(prefix.length).trim();      if (textRepeatsCommand(unprefixed, commands)) return true;    }  }   if (!textRepeatsCommand(detail, commands)) return false;   const data = asRecord(input.data);  const item = asRecord(data?.item);  const itemInput = asRecord(item?.input);  const itemResult = asRecord(item?.result);  const hasStructuredCommand = [    item?.command,    itemInput?.command,    itemResult?.command,    data?.command,  ].some((value) =>    Array.isArray(value)      ? value.some((part) => nonEmptyString(part) !== null)      : nonEmptyString(value) !== null,  );  return (    !hasStructuredCommand ||    item !== null ||    data?.toolCallId !== undefined ||    nonEmptyString(data?.kind)?.toLowerCase() === "execute"  );} function workLogEntryIsToolLike(entry: WorkLogPresentationEntry): boolean {  if (entry.tone === "tool" || entry.tone === "thinking" || entry.tone === "error") return true;  if (entry.command !== undefined && entry.command.trim().length > 0) return true;  if (entry.requestKind !== undefined) return true;  return entry.itemType !== undefined && isToolLifecycleItemType(entry.itemType);} function workLogEntryIsLocalCodeSearch(entry: WorkLogPresentationEntry): boolean {  return (    entry.itemType === "web_search" &&    /\bgrep\b/i.test(normalizeCompactToolLabel(entry.toolTitle ?? entry.label))  );} export function toolGroupAction(entry: WorkLogPresentationEntry): ToolGroupAction {  if (    entry.sourceActivityKind === "approval.requested" ||    entry.sourceActivityKind === "approval.resolved" ||    entry.sourceActivityKind === "provider.approval.respond.failed"  ) {    return "update";  }  if (resolveWorkEntryToolPresentation(entry)?.icon === "browser") return "browser";  if (    entry.requestKind === "file-read" ||    entry.itemType === "image_view" ||    entry.viewedImagePath !== undefined ||    (entry.itemType === "dynamic_tool_call" &&      entry.toolTitle?.trim().toLowerCase() === "read file")  ) {    return "read";  }  if (    entry.requestKind === "file-change" ||    entry.itemType === "file_change" ||    (entry.changedFiles?.length ?? 0) > 0  ) {    return "edit";  }  if (entry.requestKind === "command" || entry.itemType === "command_execution" || entry.command) {    return "command";  }  if (workLogEntryIsLocalCodeSearch(entry)) return "code-search";  if (entry.itemType === "web_search") return "search";  return workLogEntryIsToolLike(entry) ? "other" : "update";} export function workEntryViewedImagePath(entry: WorkLogPresentationEntry): string | null {  const viewedImagePath = entry.viewedImagePath?.trim();  if (    viewedImagePath !== undefined &&    !/[\r\n]/.test(viewedImagePath) &&    isWorkspaceImagePreviewPath(viewedImagePath)  ) {    return viewedImagePath;  }  const detail = entry.detail?.trim();  return toolGroupAction(entry) === "read" &&    detail !== undefined &&    !/[\r\n]/.test(detail) &&    isWorkspaceImagePreviewPath(detail)    ? detail    : null;} export interface ViewedImageAsset {  readonly resource: Extract<AssetResource, { readonly _tag: "media-file" }>;  readonly alt: string;  readonly srcFragment: string;} export function resolveViewedImageAsset(  source: string,  input: {    readonly threadId: ThreadId;    readonly workspaceRoot?: string | null | undefined;  },): ViewedImageAsset | null {  // A relative path with no known workspace still names a media-file relative  // to the thread's workspace, so classify against "." and drop the prefix.  const imageSource = classifyMarkdownImageSource(source, input.workspaceRoot ?? ".");  if (imageSource._tag !== "WorkspaceFile") return null;  const resolvedFilePath =    input.workspaceRoot == null && imageSource.path.startsWith("./")      ? imageSource.path.slice(2)      : imageSource.path;   const media = resolveMediaSource(source, {    threadId: input.threadId,    workspaceRoot: input.workspaceRoot,    resolvedFilePath,  });  if (media === null || media.access !== "environment") return null;  return { resource: media.resource, alt: media.name, srcFragment: media.srcFragment };} function toolGroupActionCount(  action: ToolGroupAction,  entries: ReadonlyArray<WorkLogPresentationEntry>,): number {  if (action !== "edit") return entries.length;   const changedFiles = new Set<string>();  let editsWithoutFileDetails = 0;  for (const entry of entries) {    if (!entry.changedFiles || entry.changedFiles.length === 0) {      editsWithoutFileDetails += 1;      continue;    }    for (const file of entry.changedFiles) changedFiles.add(file);  }  return changedFiles.size + editsWithoutFileDetails;} function toolGroupActionLabel(action: ToolGroupAction, count: number): string {  switch (action) {    case "read":      return `Read ${count} ${count === 1 ? "file" : "files"}`;    case "edit":      return `Changed ${count} ${count === 1 ? "file" : "files"}`;    case "command":      return `Ran ${count} ${count === 1 ? "command" : "commands"}`;    case "browser":      return `Used browser ${count} ${count === 1 ? "time" : "times"}`;    case "search":      return `Searched the web ${count} ${count === 1 ? "time" : "times"}`;    case "code-search":      return `Searched code ${count} ${count === 1 ? "time" : "times"}`;    case "other":      return `Used ${count} ${count === 1 ? "tool" : "tools"}`;    case "update":      return `Received ${count} ${count === 1 ? "update" : "updates"}`;  }} export function summarizeToolGroup(entries: ReadonlyArray<WorkLogPresentationEntry>): string {  const summaryEntries = omitSupersededLifecycleMarkers(entries, (entry) => entry);  const sources = new Map<string, ToolActivitySource>();  const groupedEntries = new Map<ToolGroupAction, WorkLogPresentationEntry[]>();  for (const entry of summaryEntries) {    if (entry.toolSource) {      sources.set(entry.toolSource.key, entry.toolSource);      continue;    }    const action = toolGroupAction(entry);    const group = groupedEntries.get(action);    if (group) group.push(entry);    else groupedEntries.set(action, [entry]);  }  const labels = [...groupedEntries].map(([action, actionEntries]) =>    toolGroupActionLabel(action, toolGroupActionCount(action, actionEntries)),  );  if (sources.size > 0) {    const sourceValues = [...sources.values()];    const sourceNames = sourceValues.map((source) => source.name);    const formattedNames =      sourceNames.length < 2        ? sourceNames[0]!        : sourceNames.length === 2          ? sourceNames.join(" and ")          : `${sourceNames.slice(0, -1).join(", ")}, and ${sourceNames.at(-1)}`;    const allIntegrations = sourceValues.every((source) => source.kind === "integration");    labels.unshift(      `Used ${formattedNames}${allIntegrations ? ` ${sources.size === 1 ? "integration" : "integrations"}` : ""}`,    );  }  const sentenceLabels = labels.map((label, index) =>    index === 0 ? label : label.charAt(0).toLowerCase() + label.slice(1),  );  if (sentenceLabels.length < 2) return sentenceLabels[0] ?? "";  if (sentenceLabels.length === 2) return sentenceLabels.join(" and ");  return `${sentenceLabels.slice(0, -1).join(", ")}, and ${sentenceLabels.at(-1)}`;} export function omitSupersededLifecycleMarkers<T>(  entries: readonly T[],  workEntryFor: (entry: T) => WorkLogPresentationEntry,): T[] {  const laterTerminalIdentities = new Set<string>();  const reversedEntries: T[] = [];   for (let index = entries.length - 1; index >= 0; index -= 1) {    const entry = entries[index]!;    const workEntry = workEntryFor(entry);    const normalizedLabel = normalizeCompactToolLabel(workEntry.toolTitle ?? workEntry.label);    const identity = [      workEntry.turnId ?? "no-turn",      workEntry.itemType ?? "",      normalizedLabel,    ].join("\u001f");    const activityKind = workEntry.sourceActivityKind;    const isStatuslessIdlessMarker =      workEntry.toolCallId === undefined &&      workEntry.toolLifecycleStatus === undefined &&      (activityKind === "tool.started" || activityKind === "tool.updated");    if (isStatuslessIdlessMarker && laterTerminalIdentities.has(identity)) continue;     reversedEntries.push(entry);    if (      activityKind === "tool.completed" ||

Lines 1–500 of 531.