@@ -662,3 +662,194 @@ export const ActiveWorkspaceWithChat: Story = {
662662 return < AppWithChatMocks /> ;
663663 } ,
664664} ;
665+
666+ /**
667+ * Tool Errors Story - Shows both error formats in GenericToolCall
668+ */
669+ export const ToolErrorsDisplay : Story = {
670+ render : ( ) => {
671+ const AppWithToolErrors = ( ) => {
672+ const initialized = useRef ( false ) ;
673+
674+ if ( ! initialized . current ) {
675+ const workspaceId = "my-app-tool-errors" ;
676+
677+ // Setup mock API
678+ setupMockAPI ( {
679+ projects : new Map ( [
680+ [
681+ "/home/user/projects/my-app" ,
682+ {
683+ path : "/home/user/projects/my-app" ,
684+ workspaces : [ ] ,
685+ } ,
686+ ] ,
687+ ] ) ,
688+ workspaces : [
689+ {
690+ id : workspaceId ,
691+ name : "tool-errors-demo" ,
692+ projectPath : "/home/user/projects/my-app" ,
693+ projectName : "my-app" ,
694+ namedWorkspacePath : "/home/user/.cmux/src/my-app/tool-errors-demo" ,
695+ } ,
696+ ] ,
697+ apiOverrides : {
698+ workspace : {
699+ create : ( ) => Promise . resolve ( { success : false , error : "Mock" } ) ,
700+ list : ( ) => Promise . resolve ( [ ] ) ,
701+ rename : ( ) => Promise . resolve ( { success : false , error : "Mock" } ) ,
702+ remove : ( ) => Promise . resolve ( { success : false , error : "Mock" } ) ,
703+ fork : ( ) => Promise . resolve ( { success : false , error : "Mock" } ) ,
704+ openTerminal : ( ) => Promise . resolve ( undefined ) ,
705+ sendMessage : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
706+ resumeStream : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
707+ interruptStream : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
708+ truncateHistory : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
709+ replaceChatHistory : ( ) => Promise . resolve ( { success : true , data : undefined } ) ,
710+ getInfo : ( ) => Promise . resolve ( null ) ,
711+ executeBash : ( ) =>
712+ Promise . resolve ( {
713+ success : true ,
714+ data : { success : true , output : "" , exitCode : 0 , wall_duration_ms : 0 } ,
715+ } ) ,
716+ onMetadata : ( ) => ( ) => undefined ,
717+ onChat : ( workspaceId , callback ) => {
718+ // Simulate chat history with various tool errors
719+ setTimeout ( ( ) => {
720+ // User message
721+ callback ( {
722+ id : "msg-1" ,
723+ role : "user" ,
724+ parts : [ { type : "text" , text : "Try calling some tools" } ] ,
725+ metadata : {
726+ historySequence : 1 ,
727+ timestamp : STABLE_TIMESTAMP - 120000 ,
728+ } ,
729+ } ) ;
730+
731+ // Assistant response with multiple tool errors
732+ callback ( {
733+ id : "msg-2" ,
734+ role : "assistant" ,
735+ parts : [
736+ {
737+ type : "text" ,
738+ text : "I'll try various tools to demonstrate error handling." ,
739+ } ,
740+ // Tool error format #1: AI SDK error (tool doesn't exist)
741+ {
742+ type : "dynamic-tool" ,
743+ toolCallId : "call-1" ,
744+ toolName : "nonexistent_tool" ,
745+ state : "output-available" ,
746+ input : {
747+ someParam : "value" ,
748+ } ,
749+ output : {
750+ error :
751+ "Tool 'nonexistent_tool' is not available. Available tools: bash, file_read, file_edit_replace_string, file_edit_insert, propose_plan, todo_write, todo_read, web_search" ,
752+ } ,
753+ } ,
754+ // Tool error format #2: Tool implementation error (bash command fails)
755+ {
756+ type : "dynamic-tool" ,
757+ toolCallId : "call-2" ,
758+ toolName : "bash" ,
759+ state : "output-available" ,
760+ input : {
761+ script : "exit 1" ,
762+ } ,
763+ output : {
764+ success : false ,
765+ error : "Command exited with code 1" ,
766+ exitCode : 1 ,
767+ wall_duration_ms : 42 ,
768+ } ,
769+ } ,
770+ // Tool error format #3: File read error
771+ {
772+ type : "dynamic-tool" ,
773+ toolCallId : "call-3" ,
774+ toolName : "file_read" ,
775+ state : "output-available" ,
776+ input : {
777+ filePath : "/nonexistent/file.txt" ,
778+ } ,
779+ output : {
780+ success : false ,
781+ error : "ENOENT: no such file or directory, open '/nonexistent/file.txt'" ,
782+ } ,
783+ } ,
784+ // Tool error format #4: File edit with WRITE DENIED (should be collapsed)
785+ {
786+ type : "dynamic-tool" ,
787+ toolCallId : "call-4" ,
788+ toolName : "file_edit_replace_string" ,
789+ state : "output-available" ,
790+ input : {
791+ file_path : "src/test.ts" ,
792+ old_string : "const x = 1;" ,
793+ new_string : "const x = 2;" ,
794+ } ,
795+ output : {
796+ success : false ,
797+ error :
798+ "WRITE DENIED, FILE UNMODIFIED: File has been modified since it was read. Please re-read the file and try again." ,
799+ } ,
800+ } ,
801+ // Tool error format #5: Policy disabled tool
802+ {
803+ type : "dynamic-tool" ,
804+ toolCallId : "call-5" ,
805+ toolName : "file_edit_insert" ,
806+ state : "output-available" ,
807+ input : {
808+ file_path : "src/new.ts" ,
809+ line_offset : 0 ,
810+ content : "console.log('test');" ,
811+ } ,
812+ output : {
813+ error :
814+ "Tool execution skipped because the requested tool is disabled by policy." ,
815+ } ,
816+ } ,
817+ {
818+ type : "text" ,
819+ text : "As you can see, various tool errors are displayed with clear error messages and 'failed' status." ,
820+ } ,
821+ ] ,
822+ metadata : {
823+ historySequence : 2 ,
824+ timestamp : STABLE_TIMESTAMP - 110000 ,
825+ model : "anthropic:claude-sonnet-4-20250514" ,
826+ } ,
827+ } ) ;
828+ } , 100 ) ;
829+
830+ return ( ) => undefined ;
831+ } ,
832+ } ,
833+ } ,
834+ } ) ;
835+
836+ // Set initial workspace selection
837+ localStorage . setItem (
838+ "selectedWorkspace" ,
839+ JSON . stringify ( {
840+ workspaceId : workspaceId ,
841+ projectPath : "/home/user/projects/my-app" ,
842+ projectName : "my-app" ,
843+ namedWorkspacePath : "/home/user/.cmux/src/my-app/tool-errors-demo" ,
844+ } )
845+ ) ;
846+
847+ initialized . current = true ;
848+ }
849+
850+ return < AppLoader /> ;
851+ } ;
852+
853+ return < AppWithToolErrors /> ;
854+ } ,
855+ } ;
0 commit comments