mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix: hide empty execute tool calls (#25346)
This commit is contained in:
@@ -32,6 +32,21 @@ export const ShortCommand: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const RunningWithoutCommand: Story = {
|
||||
args: {
|
||||
command: "",
|
||||
status: "running",
|
||||
output: "",
|
||||
},
|
||||
play: async ({ canvasElement }) => {
|
||||
const canvas = within(canvasElement);
|
||||
expect(canvas.queryByText("$")).not.toBeInTheDocument();
|
||||
expect(
|
||||
canvas.queryByRole("button", { name: "Copy command" }),
|
||||
).not.toBeInTheDocument();
|
||||
},
|
||||
};
|
||||
|
||||
export const LongCommand: Story = {
|
||||
args: {
|
||||
command: longCommand,
|
||||
|
||||
@@ -77,6 +77,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
|
||||
killedBySignal,
|
||||
outputInitiallyOpen,
|
||||
}) => {
|
||||
const hasCommand = command.trim().length > 0;
|
||||
const hasOutput = output.length > 0;
|
||||
const isRunning = status === "running";
|
||||
const showFailureIndicator = isError && !isRunning;
|
||||
@@ -86,8 +87,12 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
|
||||
: "Expand command output";
|
||||
const durationLabel = formatShellDurationMs(durationMs);
|
||||
|
||||
if (!hasCommand) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="group/exec grid w-full grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2 rounded-md bg-surface-primary font-mono text-xs leading-5">
|
||||
<div className="group/exec grid w-full grid-cols-[minmax(0,1fr)_auto] items-start gap-x-2 rounded-md bg-surface-primary font-mono font-normal text-xs leading-5">
|
||||
<Tooltip delayDuration={300}>
|
||||
<TooltipTrigger asChild>
|
||||
{hasOutput ? (
|
||||
@@ -96,7 +101,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
|
||||
aria-expanded={outputOpen}
|
||||
aria-label={outputToggleLabel}
|
||||
onClick={() => setOutputOpen((value) => !value)}
|
||||
className="col-start-1 row-start-1 m-0 flex w-full min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left font-[inherit] text-[inherit] text-content-secondary transition-colors hover:text-content-primary"
|
||||
className="col-start-1 row-start-1 m-0 flex w-full min-w-0 cursor-pointer items-center gap-2 border-0 bg-transparent p-0 text-left font-[inherit] font-normal text-[inherit] text-content-secondary transition-colors hover:text-content-primary"
|
||||
>
|
||||
<ShellCommandLine
|
||||
command={command}
|
||||
@@ -105,7 +110,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
|
||||
/>
|
||||
</button>
|
||||
) : (
|
||||
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-2 text-content-secondary">
|
||||
<div className="col-start-1 row-start-1 flex min-w-0 items-center gap-2 font-normal text-content-secondary">
|
||||
<ShellCommandLine
|
||||
command={command}
|
||||
durationLabel={durationLabel}
|
||||
@@ -113,7 +118,7 @@ const ExecuteToolInner: React.FC<ExecuteToolInnerProps> = ({
|
||||
</div>
|
||||
)}
|
||||
</TooltipTrigger>
|
||||
<TooltipContent className="max-w-xl whitespace-pre-wrap break-all font-mono">
|
||||
<TooltipContent className="max-w-xl whitespace-pre-wrap break-all font-mono font-normal">
|
||||
{command}
|
||||
</TooltipContent>
|
||||
</Tooltip>
|
||||
@@ -182,12 +187,14 @@ const ShellCommandLine: React.FC<{
|
||||
}> = ({ command, durationLabel, expanded }) => {
|
||||
return (
|
||||
<>
|
||||
<span className="shrink-0 text-[13px] text-content-success">$</span>
|
||||
<span className="block min-w-0 truncate text-[13px] text-content-primary">
|
||||
<span className="shrink-0 text-[13px] font-normal text-content-success">
|
||||
$
|
||||
</span>
|
||||
<span className="block min-w-0 truncate text-[13px] font-normal text-content-primary">
|
||||
{command}
|
||||
</span>
|
||||
{durationLabel && (
|
||||
<span className="shrink-0 text-[13px] text-content-secondary">
|
||||
<span className="shrink-0 text-[13px] font-normal text-content-secondary">
|
||||
{durationLabel}
|
||||
</span>
|
||||
)}
|
||||
|
||||
@@ -214,14 +214,19 @@ const parseAskUserQuestionResult = (
|
||||
return null;
|
||||
};
|
||||
|
||||
const ExecuteRenderer: FC<ToolRendererProps> = ({
|
||||
status,
|
||||
args,
|
||||
result,
|
||||
isError,
|
||||
killedBySignal,
|
||||
shellToolDisplayMode,
|
||||
}) => {
|
||||
type ExecuteRenderData = {
|
||||
command: string;
|
||||
output: string;
|
||||
durationMs?: number;
|
||||
isBackgrounded: boolean;
|
||||
authenticateURL: string;
|
||||
providerLabel: string;
|
||||
};
|
||||
|
||||
const getExecuteRenderData = (
|
||||
args: unknown,
|
||||
result: unknown,
|
||||
): ExecuteRenderData => {
|
||||
const parsedArgs = parseArgs(args);
|
||||
const command = parsedArgs ? asString(parsedArgs.command) : "";
|
||||
const rec = asRecord(result);
|
||||
@@ -233,32 +238,61 @@ const ExecuteRenderer: FC<ToolRendererProps> = ({
|
||||
const isBackgrounded = Boolean(
|
||||
rec && asString(rec.background_process_id).trim(),
|
||||
);
|
||||
const authRequired = rec ? Boolean(rec.auth_required) : false;
|
||||
const authenticateURL = rec ? asString(rec.authenticate_url).trim() : "";
|
||||
const authenticateURL = rec?.auth_required
|
||||
? asString(rec.authenticate_url).trim()
|
||||
: "";
|
||||
const providerLabel = toProviderLabel(
|
||||
rec ? asString(rec.provider_display_name).trim() : "",
|
||||
rec ? asString(rec.provider_id).trim() : "",
|
||||
rec ? asString(rec.provider_type).trim() : "",
|
||||
);
|
||||
|
||||
if (authRequired && authenticateURL) {
|
||||
return {
|
||||
command,
|
||||
output,
|
||||
durationMs,
|
||||
isBackgrounded,
|
||||
authenticateURL,
|
||||
providerLabel,
|
||||
};
|
||||
};
|
||||
|
||||
const shouldHideExecuteTool = (data: ExecuteRenderData): boolean => {
|
||||
return data.command.trim().length === 0 && !data.authenticateURL;
|
||||
};
|
||||
|
||||
const ExecuteRenderer: FC<ToolRendererProps> = ({
|
||||
status,
|
||||
args,
|
||||
result,
|
||||
isError,
|
||||
killedBySignal,
|
||||
shellToolDisplayMode,
|
||||
}) => {
|
||||
const data = getExecuteRenderData(args, result);
|
||||
|
||||
if (shouldHideExecuteTool(data)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (data.authenticateURL) {
|
||||
return (
|
||||
<ExecuteAuthRequiredTool
|
||||
command={command}
|
||||
output={output}
|
||||
authenticateURL={authenticateURL}
|
||||
providerLabel={providerLabel}
|
||||
command={data.command}
|
||||
output={data.output}
|
||||
authenticateURL={data.authenticateURL}
|
||||
providerLabel={data.providerLabel}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<ExecuteToolComponent
|
||||
command={command}
|
||||
output={output}
|
||||
command={data.command}
|
||||
output={data.output}
|
||||
status={status}
|
||||
isError={isError}
|
||||
durationMs={durationMs}
|
||||
isBackgrounded={isBackgrounded}
|
||||
durationMs={data.durationMs}
|
||||
isBackgrounded={data.isBackgrounded}
|
||||
killedBySignal={killedBySignal}
|
||||
shellToolDisplayMode={shellToolDisplayMode}
|
||||
/>
|
||||
@@ -1059,6 +1093,12 @@ export const Tool = memo(
|
||||
? SubagentRenderer
|
||||
: (toolRenderers[name] ?? GenericToolRenderer);
|
||||
const isShellTool = name === "execute" || name === "process_output";
|
||||
if (
|
||||
name === "execute" &&
|
||||
shouldHideExecuteTool(getExecuteRenderData(args, result))
|
||||
) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
|
||||
Reference in New Issue
Block a user