Improve the formatting of tool calls, show thinking, treat Reasoning and Thinking as the same thing (sorry Kant) (#7626)
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -670,8 +670,6 @@ export type MessageContent = (TextContent & {
|
||||
type: 'redactedThinking';
|
||||
}) | (SystemNotificationContent & {
|
||||
type: 'systemNotification';
|
||||
}) | (ReasoningContent & {
|
||||
type: 'reasoning';
|
||||
});
|
||||
|
||||
export type MessageEvent = {
|
||||
@@ -1009,10 +1007,6 @@ export type ReadResourceResponse = {
|
||||
uri: string;
|
||||
};
|
||||
|
||||
export type ReasoningContent = {
|
||||
text: string;
|
||||
};
|
||||
|
||||
export type Recipe = {
|
||||
activities?: Array<string> | null;
|
||||
author?: Author | null;
|
||||
|
||||
@@ -5,7 +5,7 @@ import MarkdownContent from './MarkdownContent';
|
||||
import ToolCallWithResponse from './ToolCallWithResponse';
|
||||
import {
|
||||
getTextAndImageContent,
|
||||
getReasoningContent,
|
||||
getThinkingContent,
|
||||
getToolRequests,
|
||||
getToolResponses,
|
||||
getToolConfirmationContent,
|
||||
@@ -48,7 +48,7 @@ export default function GooseMessage({
|
||||
const contentRef = useRef<HTMLDivElement | null>(null);
|
||||
|
||||
let { textContent, imagePaths } = getTextAndImageContent(message);
|
||||
const reasoningContent = getReasoningContent(message);
|
||||
const thinkingContent = getThinkingContent(message);
|
||||
|
||||
const splitChainOfThought = (text: string): { displayText: string; cotText: string | null } => {
|
||||
const regex = /<think>([\s\S]*?)<\/think>/i;
|
||||
@@ -131,26 +131,16 @@ export default function GooseMessage({
|
||||
return (
|
||||
<div className="goose-message flex w-[90%] justify-start min-w-0">
|
||||
<div className="flex flex-col w-full min-w-0">
|
||||
{reasoningContent && (
|
||||
<details className="mb-2">
|
||||
<summary className="cursor-pointer text-xs text-textSubtle select-none">
|
||||
Show reasoning
|
||||
</summary>
|
||||
<div className="mt-2 text-sm">
|
||||
<MarkdownContent content={reasoningContent} />
|
||||
</div>
|
||||
</details>
|
||||
{thinkingContent && (
|
||||
<div className="mb-2 text-xs text-gray-400/70 italic">
|
||||
<MarkdownContent content={thinkingContent} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{cotText && (
|
||||
<details className="bg-background-secondary border border-border-primary rounded p-2 mb-2">
|
||||
<summary className="cursor-pointer text-sm text-text-secondary select-none">
|
||||
Show thinking
|
||||
</summary>
|
||||
<div className="mt-2">
|
||||
<MarkdownContent content={cotText} />
|
||||
</div>
|
||||
</details>
|
||||
<div className="mb-2 text-sm text-gray-400 italic">
|
||||
<MarkdownContent content={cotText} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{(displayText.trim() || imagePaths.length > 0) && (
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useState } from 'react';
|
||||
import MarkdownContent from './MarkdownContent';
|
||||
import Expand from './ui/Expand';
|
||||
|
||||
export type ToolCallArgumentValue =
|
||||
@@ -14,6 +13,12 @@ interface ToolCallArgumentsProps {
|
||||
args: Record<string, ToolCallArgumentValue>;
|
||||
}
|
||||
|
||||
function formatValue(value: ToolCallArgumentValue): string {
|
||||
if (typeof value === 'string') return value;
|
||||
if (typeof value === 'object' && value !== null) return JSON.stringify(value, null, 2);
|
||||
return String(value);
|
||||
}
|
||||
|
||||
export function ToolCallArguments({ args }: ToolCallArgumentsProps) {
|
||||
const [expandedKeys, setExpandedKeys] = useState<Record<string, boolean>>({});
|
||||
|
||||
@@ -22,46 +27,33 @@ export function ToolCallArguments({ args }: ToolCallArgumentsProps) {
|
||||
};
|
||||
|
||||
const renderValue = (key: string, value: ToolCallArgumentValue) => {
|
||||
if (typeof value === 'string') {
|
||||
const needsExpansion = value.length > 60;
|
||||
const isExpanded = expandedKeys[key];
|
||||
const text = formatValue(value).trim();
|
||||
const needsExpansion = text.length > 60 || text.includes('\n');
|
||||
const isExpanded = expandedKeys[key];
|
||||
|
||||
if (!needsExpansion) {
|
||||
return (
|
||||
<div className="font-sans text-sm mb-2">
|
||||
<div className="flex flex-row">
|
||||
<span className="text-text-secondary min-w-[140px]">{key}</span>
|
||||
<span className="text-text-secondary">{value}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={`font-sans text-sm mb-2 ${isExpanded ? '' : 'truncate min-w-0'}`}>
|
||||
<div className={`flex flex-row items-stretch ${isExpanded ? '' : 'truncate min-w-0'}`}>
|
||||
<button
|
||||
onClick={() => toggleKey(key)}
|
||||
className="flex text-left text-text-secondary min-w-[140px]"
|
||||
>
|
||||
<span>{key}</span>
|
||||
</button>
|
||||
<div className={`w-full flex items-stretch ${isExpanded ? '' : 'truncate min-w-0'}`}>
|
||||
{isExpanded ? (
|
||||
<div>
|
||||
<MarkdownContent
|
||||
content={value}
|
||||
className="font-sans text-sm text-text-secondary"
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => toggleKey(key)}
|
||||
className={`text-left text-text-secondary ${isExpanded ? '' : 'truncate min-w-0'}`}
|
||||
>
|
||||
{value}
|
||||
</button>
|
||||
)}
|
||||
return (
|
||||
<div className="font-sans text-sm mb-2">
|
||||
<div className={`flex flex-row items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}>
|
||||
<button
|
||||
onClick={() => needsExpansion && toggleKey(key)}
|
||||
className={`flex text-left text-text-secondary min-w-[140px] ${needsExpansion ? 'cursor-pointer' : 'cursor-default'}`}
|
||||
>
|
||||
<span>{key}</span>
|
||||
</button>
|
||||
<div className={`w-full flex items-stretch ${!isExpanded && needsExpansion ? 'truncate min-w-0' : ''}`}>
|
||||
{isExpanded ? (
|
||||
<pre className="font-mono text-xs text-text-secondary whitespace-pre-wrap max-w-full overflow-x-auto">
|
||||
{text}
|
||||
</pre>
|
||||
) : (
|
||||
<button
|
||||
onClick={() => needsExpansion && toggleKey(key)}
|
||||
className={`text-left text-text-secondary font-mono text-xs ${needsExpansion ? 'truncate min-w-0 cursor-pointer' : 'cursor-default'}`}
|
||||
>
|
||||
{text.split('\n')[0]}
|
||||
</button>
|
||||
)}
|
||||
{needsExpansion && (
|
||||
<button
|
||||
onClick={() => toggleKey(key)}
|
||||
className="flex flex-row items-stretch grow text-text-secondary pr-2"
|
||||
@@ -69,27 +61,9 @@ export function ToolCallArguments({ args }: ToolCallArgumentsProps) {
|
||||
<div className="min-w-2 grow" />
|
||||
<Expand size={5} isExpanded={isExpanded} />
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
// Handle non-string values (arrays, objects, etc.)
|
||||
const content = Array.isArray(value)
|
||||
? value.map((item, index) => `${index + 1}. ${JSON.stringify(item)}`).join('\n')
|
||||
: typeof value === 'object' && value !== null
|
||||
? JSON.stringify(value, null, 2)
|
||||
: String(value);
|
||||
|
||||
return (
|
||||
<div className="mb-2">
|
||||
<div className="flex flex-row font-sans text-sm">
|
||||
<span className="text-text-secondary min-w-[140px]">{key}</span>
|
||||
<pre className="whitespace-pre-wrap text-text-secondary overflow-x-auto max-w-full">
|
||||
{content}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -866,7 +866,7 @@ interface ToolResultViewProps {
|
||||
isStartExpanded: boolean;
|
||||
}
|
||||
|
||||
function ToolResultView({ toolCall, result, isStartExpanded }: ToolResultViewProps) {
|
||||
function ToolResultView({ result, isStartExpanded }: ToolResultViewProps) {
|
||||
const hasText = (c: ContentBlock): c is ContentBlock & { text: string } =>
|
||||
'text' in c && typeof (c as Record<string, unknown>).text === 'string';
|
||||
|
||||
@@ -879,18 +879,6 @@ function ToolResultView({ toolCall, result, isStartExpanded }: ToolResultViewPro
|
||||
const hasResource = (c: ContentBlock): c is ContentBlock & { resource: unknown } =>
|
||||
'resource' in c;
|
||||
|
||||
const wrapMarkdown = (text: string): string => {
|
||||
if (
|
||||
['code_execution__list_functions', 'code_execution__get_function_details'].includes(
|
||||
toolCall.name
|
||||
)
|
||||
) {
|
||||
return '```typescript\n' + text + '\n```';
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<ToolCallExpandable
|
||||
label={<span className="pl-4 py-1 font-sans text-sm">Output</span>}
|
||||
@@ -898,10 +886,9 @@ function ToolResultView({ toolCall, result, isStartExpanded }: ToolResultViewPro
|
||||
>
|
||||
<div className="pl-4 pr-4 py-4">
|
||||
{hasText(result) && (
|
||||
<MarkdownContent
|
||||
content={wrapMarkdown(result.text)}
|
||||
className="whitespace-pre-wrap max-w-full overflow-x-auto"
|
||||
/>
|
||||
<pre className="font-mono text-xs whitespace-pre-wrap max-w-full overflow-x-auto">
|
||||
{result.text.trim()}
|
||||
</pre>
|
||||
)}
|
||||
{hasImage(result) && (
|
||||
<img
|
||||
|
||||
@@ -8,7 +8,7 @@ import ToolCallWithResponse from '../ToolCallWithResponse';
|
||||
import ImagePreview from '../ImagePreview';
|
||||
import {
|
||||
getTextAndImageContent,
|
||||
getReasoningContent,
|
||||
getThinkingContent,
|
||||
ToolRequestMessageContent,
|
||||
ToolResponseMessageContent,
|
||||
} from '../../types/message';
|
||||
@@ -83,7 +83,7 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({
|
||||
messages
|
||||
.map((message, index) => {
|
||||
const { textContent, imagePaths } = getTextAndImageContent(message);
|
||||
const reasoningContent = getReasoningContent(message);
|
||||
const thinkingContent = getThinkingContent(message);
|
||||
|
||||
// Get tool requests from the message
|
||||
const toolRequests = message.content
|
||||
@@ -121,16 +121,11 @@ export const SessionMessages: React.FC<SessionMessagesProps> = ({
|
||||
</div>
|
||||
|
||||
<div className="flex flex-col w-full">
|
||||
{/* Reasoning content */}
|
||||
{reasoningContent && (
|
||||
<details className="mb-2">
|
||||
<summary className="cursor-pointer text-xs text-textSubtle select-none">
|
||||
Show reasoning
|
||||
</summary>
|
||||
<div className="mt-2 text-sm">
|
||||
<MarkdownContent content={reasoningContent} />
|
||||
</div>
|
||||
</details>
|
||||
{/* Thinking content */}
|
||||
{thinkingContent && (
|
||||
<div className="mb-2 text-sm text-gray-400 italic">
|
||||
<MarkdownContent content={thinkingContent} />
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Text content */}
|
||||
|
||||
@@ -187,6 +187,21 @@ function pushMessage(currentMessages: Message[], incomingMsg: Message): Message[
|
||||
incomingMsg.content.length === 1
|
||||
) {
|
||||
lastContent.text += newContent.text;
|
||||
} else if (
|
||||
lastContent?.type === 'thinking' &&
|
||||
newContent?.type === 'thinking' &&
|
||||
incomingMsg.content.length === 1 &&
|
||||
'thinking' in lastContent &&
|
||||
'thinking' in newContent
|
||||
) {
|
||||
// For thinking blocks: if the new block has a signature, it's the complete
|
||||
// block from content_block_stop — replace entirely. Otherwise append the delta.
|
||||
if ('signature' in newContent && newContent.signature) {
|
||||
lastContent.thinking = newContent.thinking;
|
||||
lastContent.signature = newContent.signature;
|
||||
} else {
|
||||
lastContent.thinking += newContent.thinking;
|
||||
}
|
||||
} else {
|
||||
lastMsg.content.push(...incomingMsg.content);
|
||||
}
|
||||
|
||||
@@ -97,16 +97,16 @@ export function getTextAndImageContent(message: Message): {
|
||||
return { textContent, imagePaths };
|
||||
}
|
||||
|
||||
export function getReasoningContent(message: Message): string | null {
|
||||
const reasoningContents = message.content
|
||||
.filter((content) => content.type === 'reasoning')
|
||||
export function getThinkingContent(message: Message): string | null {
|
||||
const thinkingContents = message.content
|
||||
.filter((content) => content.type === 'thinking')
|
||||
.map((content) => {
|
||||
if ('text' in content) return content.text;
|
||||
if ('thinking' in content) return content.thinking;
|
||||
return '';
|
||||
})
|
||||
.filter((text) => text.length > 0);
|
||||
|
||||
return reasoningContents.length > 0 ? reasoningContents.join('') : null;
|
||||
return thinkingContents.length > 0 ? thinkingContents.join('') : null;
|
||||
}
|
||||
|
||||
export function getToolRequests(message: Message): (ToolRequest & { type: 'toolRequest' })[] {
|
||||
|
||||
Reference in New Issue
Block a user