import type { ToolCallDiffTarget } from "../../diffPanelStore"; import type { EnvironmentId, OrchestrationV2ProjectedTurnItem } from "@t3tools/contracts"; import { isJsonToolText, toolCallLines, turnItemDetailRevision, turnItemNeedsDetailFetch, turnItemOutputImages, turnItemOutputText, } from "@t3tools/client-runtime/work-log/item-detail"; import { formatDiffCount } from "@t3tools/client-runtime/diff-count"; import { isJavaScriptReplTool } from "@t3tools/shared/toolActivity"; import { ExternalLinkIcon } from "lucide-react"; import { memo, type ReactNode, Suspense, use } from "react"; import { useRenderedAppearance } from "../../hooks/useTheme"; import { cn } from "../../lib/utils"; import { resolveDiffThemeName } from "../../lib/diffRendering"; import { getSyntaxHighlighterPromise } from "../../lib/syntaxHighlighting"; import { useTurnItemDetail } from "../../state/queries"; import { formatWorkspaceRelativePath } from "../../filePathDisplay"; import { Button } from "../ui/button"; import ChatMarkdown from "../ChatMarkdown"; import { ChatMarkdownAssetImage } from "./ChatMarkdownMedia"; import { RenderErrorBoundary } from "../RenderErrorBoundary"; import { CommandOutputPanel, useCommandOutputStreaming } from "./CommandOutputPanel"; import { resolveExternalWebLinkHref } from "./externalLinkContextMenu"; import type { ExpandedImagePreview } from "./ExpandedImagePreview"; import { ShellCommandBlock } from "./ShellCommandBlock"; import type { TurnItemSource } from "./timelineRowContext"; interface V2ItemInspectorProps { readonly projectedItem: OrchestrationV2ProjectedTurnItem; readonly environmentId: EnvironmentId; readonly cwd?: string | undefined; readonly workspaceRoot?: string | undefined; readonly onOpenToolDiff?: ((target: ToolCallDiffTarget) => void) | undefined; readonly onImageExpand?: ((preview: ExpandedImagePreview) => void) | undefined; readonly source?: TurnItemSource | undefined; } function SyntaxTokens({ text, language, }: { readonly text: string; readonly language: "json" | "typescript" | "html"; }) { const resolvedTheme = useRenderedAppearance(); const highlighter = use(getSyntaxHighlighterPromise(language)); const { tokens } = highlighter.codeToTokens(text, { lang: language, theme: resolveDiffThemeName(resolvedTheme), }); return tokens.flatMap((line, lineIndex) => [ lineIndex > 0 ? "\n" : "", ...line.map((token) => ( {token.content} )), ]); } const monoClassName = "font-mono text-(length:--font-size-code,var(--text-2xs)) leading-relaxed whitespace-pre-wrap break-words select-text"; function StructuredValue({ value, highlightJson = false, language, }: { readonly value: unknown; readonly highlightJson?: boolean; readonly language?: "typescript" | "html" | "json"; }) { const text = typeof value === "string" ? value : JSON.stringify(value, null, 2); const isJson = highlightJson && isJsonToolText(text); if (!text) return null; return (
      {language || isJson ? (
        
          
            
          
        
      ) : (
        text
      )}
    
); } function useFetchedTurnItem( projectedItem: OrchestrationV2ProjectedTurnItem, environmentId: EnvironmentId, source: TurnItemSource | undefined, skip = false, ) { const wireItem = projectedItem.item; const fetches = !skip && turnItemNeedsDetailFetch(wireItem); const detail = useTurnItemDetail( fetches ? { environmentId, threadId: projectedItem.sourceThreadId, itemId: projectedItem.sourceItemId, revision: turnItemDetailRevision(wireItem), } : null, source?.detail, ); const fetchedItem = detail.data?.item; const item = fetchedItem?.type === wireItem.type ? fetchedItem : wireItem; return { item, output: { output: turnItemOutputText(item), images: turnItemOutputImages(item), environmentId, pending: item === wireItem && detail.isPending, error: item !== wireItem ? null : detail.data?.item === null ? "Output is no longer available." : detail.error, empty: fetches && item !== wireItem, }, }; } interface ToolOutputState { readonly output: string | null; readonly images: ReturnType; readonly environmentId: EnvironmentId; readonly pending: boolean; readonly error: string | null; readonly empty: boolean; readonly onImageExpand?: ((preview: ExpandedImagePreview) => void) | undefined; } function ToolOutputImages(props: ToolOutputState) { return props.images.map((resource) => ( )); } function ToolOutput(props: ToolOutputState) { return props.output ? ( ) : props.pending ? (
Loading output…
) : props.error ? (
Couldn't load output: {props.error}
) : props.empty && props.images.length === 0 ? (
No output.
) : null; } export function FetchedToolOutput(props: { readonly projectedItem: OrchestrationV2ProjectedTurnItem; readonly environmentId: EnvironmentId; readonly onImageExpand?: ((preview: ExpandedImagePreview) => void) | undefined; readonly source?: TurnItemSource | undefined; }) { const { output } = useFetchedTurnItem(props.projectedItem, props.environmentId, props.source); return (
); } function ToolCallBody( props: ToolOutputState & { readonly command?: string; readonly args?: unknown; readonly toolName?: string | null; readonly exitCode?: number | undefined; /** Replaces fetched output. */ readonly outputSlot?: ReactNode; }, ) { const call = toolCallLines({ command: props.command, args: props.args }); return (
{call.command ? : null} {call.args ? (
{call.args.map(([key, value]) => (
{key} {key === "html" ? ( ) : key === "code" && isJavaScriptReplTool(props.toolName) ? ( ) : isJsonToolText(value) ? ( ) : ( value )}
))}
) : null} {call.argsText ? : null} {props.outputSlot ?? } {props.exitCode !== undefined && props.exitCode !== 0 ? (
exit {props.exitCode}
) : null}
); } export const V2ItemInspector = memo(function V2ItemInspector(props: V2ItemInspectorProps) { // Command rows stream their output, including the final output, from the server. const environmentStreamsOutput = useCommandOutputStreaming(props.environmentId); const streamsOutput = (props.source !== undefined || environmentStreamsOutput) && props.projectedItem.item.type === "command_execution"; const fetched = useFetchedTurnItem( props.projectedItem, props.environmentId, props.source, streamsOutput, ); const item = fetched.item; const outputState = fetched.output; return (
{item.type === "reasoning" && item.text ? (
) : null} {item.type === "command_execution" ? ( ) : undefined } /> ) : null} {item.type === "file_change" ? (
{formatWorkspaceRelativePath(item.fileName, props.workspaceRoot)} {item.additions !== undefined || item.deletions !== undefined ? ( +{formatDiffCount(item.additions ?? 0)}{" "} -{formatDiffCount(item.deletions ?? 0)} ) : null} {props.onOpenToolDiff ? ( ) : null}
{item.status === "failed" && item.diffStr?.trim() ? ( ) : null} {item.changes !== undefined && item.changes.length > 0 ? (
    {item.changes.map((change) => (
  • {change.operation} {change.oldPath ? `${change.oldPath} → ` : ""} {formatWorkspaceRelativePath(change.path, props.workspaceRoot)} {change.fileType || change.mimeType ? ` (${[change.fileType, change.mimeType].filter(Boolean).join(", ")})` : ""}
  • ))}
) : null}
) : null} {item.type === "file_search" && item.pattern?.trim() ? (
{item.pattern}
) : null} {item.type === "web_search" && item.patterns?.length ? (
{item.patterns.join("\n")}
) : null} {item.type === "file_search" && item.results?.length ? (
    {item.results.map((result) => (
  • {formatWorkspaceRelativePath(result.fileName, props.workspaceRoot)} {result.line === undefined ? "" : `:${result.line}`} {result.column === undefined ? "" : `:${result.column}`} {result.preview ? (

    {result.preview}

    ) : null}
  • ))}
) : null} {item.type === "web_search" && item.results?.length ? (
    {item.results.map((result) => { const safeHref = resolveExternalWebLinkHref(result.url); return (
  • {safeHref ? ( {result.title ?? result.url} ) : (

    {result.title ?? result.url ?? "Search result"}

    )} {result.snippet ?

    {result.snippet}

    : null}
  • ); })}
) : null} {item.type === "dynamic_tool" ? ( ) : null} {item.type === "approval_request" ? : null} {item.type === "user_input_request" ? ( [question.question, ...question.options.map((option) => `• ${option.label}`)].join( "\n", ), ) .join("\n\n")} /> ) : null} {item.type === "notification" ? : null} {item.type === "system_notice" ? : null} {item.type === "error" ? : null} {item.type === "todo_list" ? ( `${step.status === "completed" ? "✓" : step.status === "running" ? "◉" : "○"} ${step.text}`, ) .join("\n")} /> ) : null}
); });