mirror of https://github.com/buster-so/buster.git
read file messages
This commit is contained in:
parent
775b09b6a3
commit
bd195df18f
|
@ -0,0 +1,97 @@
|
|||
import { Box, Text, useInput } from 'ink';
|
||||
import React, { useState } from 'react';
|
||||
import path from 'node:path';
|
||||
import type { AgentMessage } from '../services/analytics-engineer-handler';
|
||||
|
||||
interface ReadMessageProps {
|
||||
message: Extract<AgentMessage, { kind: 'read' }>;
|
||||
}
|
||||
|
||||
/**
|
||||
* Component for displaying file read operations
|
||||
* Shows READ badge, relative file path, and file content preview
|
||||
* Supports expansion with Ctrl+O to show full content
|
||||
*/
|
||||
export function ReadMessage({ message }: ReadMessageProps) {
|
||||
const [isExpanded, setIsExpanded] = useState(false);
|
||||
|
||||
// Handle Ctrl+O to toggle expansion
|
||||
useInput((input, key) => {
|
||||
if (key.ctrl && input === 'o') {
|
||||
setIsExpanded((prev) => !prev);
|
||||
}
|
||||
});
|
||||
|
||||
const { args, result } = message;
|
||||
|
||||
if (!result) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get relative path from cwd
|
||||
const relativePath = path.relative(process.cwd(), result.file_path);
|
||||
|
||||
// Handle error case
|
||||
if (result.status === 'error') {
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
<Box flexDirection="row">
|
||||
<Text bold color="white" backgroundColor="blue">
|
||||
READ
|
||||
</Text>
|
||||
<Text color="#94a3b8"> ({relativePath})</Text>
|
||||
</Box>
|
||||
<Box paddingLeft={2}>
|
||||
<Text color="red" dimColor>
|
||||
↳ Error: {result.error_message}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
// Split content into lines
|
||||
const contentLines = result.content?.split('\n') || [];
|
||||
|
||||
// Show first 5 lines when not expanded, all lines when expanded
|
||||
const displayLines = isExpanded ? contentLines : contentLines.slice(0, 5);
|
||||
|
||||
return (
|
||||
<Box flexDirection="column">
|
||||
{/* READ badge with relative file path */}
|
||||
<Box flexDirection="row">
|
||||
<Text bold color="white" backgroundColor="blue">
|
||||
READ
|
||||
</Text>
|
||||
<Text color="#94a3b8"> ({relativePath})</Text>
|
||||
</Box>
|
||||
|
||||
{/* File content lines - always show with indentation */}
|
||||
{contentLines.length > 0 && (
|
||||
<Box flexDirection="column" paddingLeft={2}>
|
||||
{displayLines.map((line, idx) => (
|
||||
<Text key={idx} color="#e0e7ff">
|
||||
{line}
|
||||
</Text>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Expansion hint if content is long */}
|
||||
{contentLines.length > 5 && (
|
||||
<Box paddingLeft={2}>
|
||||
<Text color="#64748b" dimColor>
|
||||
{isExpanded ? '(Press Ctrl+O to collapse)' : `... +${contentLines.length - 5} lines (Press Ctrl+O to expand)`}
|
||||
</Text>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Status line with indentation */}
|
||||
<Box paddingLeft={2}>
|
||||
<Text color="#64748b" dimColor>
|
||||
↳ Read {contentLines.length} lines{result.truncated ? ' (truncated at 1000 lines)' : ''}
|
||||
</Text>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
}
|
|
@ -4,6 +4,7 @@ import type { AgentMessage } from '../services/analytics-engineer-handler';
|
|||
import { ExecuteMessage } from './execute-message';
|
||||
import { WriteMessage } from './write-message';
|
||||
import { EditMessage } from './edit-message';
|
||||
import { ReadMessage } from './read-message';
|
||||
|
||||
interface AgentMessageComponentProps {
|
||||
message: AgentMessage;
|
||||
|
@ -52,6 +53,10 @@ export function AgentMessageComponent({ message }: AgentMessageComponentProps) {
|
|||
// For edit operations, use the EditMessage component
|
||||
return <EditMessage message={message} />;
|
||||
|
||||
case 'read':
|
||||
// For read operations, use the ReadMessage component
|
||||
return <ReadMessage message={message} />;
|
||||
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -48,6 +48,12 @@ export type AgentMessage =
|
|||
event: 'start' | 'complete';
|
||||
args: { filePath: string; oldString?: string; newString?: string; edits?: Array<{ oldString: string; newString: string }> };
|
||||
result?: { success: boolean; filePath: string; diff?: string; finalDiff?: string; message?: string; errorMessage?: string };
|
||||
}
|
||||
| {
|
||||
kind: 'read';
|
||||
event: 'start' | 'complete';
|
||||
args: { filePath: string };
|
||||
result?: { status: 'success' | 'error'; file_path: string; content?: string; truncated?: boolean; error_message?: string };
|
||||
};
|
||||
|
||||
export interface DocsAgentMessage {
|
||||
|
@ -158,6 +164,18 @@ export async function runDocsAgent(params: RunDocsAgentParams) {
|
|||
},
|
||||
});
|
||||
}
|
||||
|
||||
// Handle read tool events - only show complete to avoid duplicates
|
||||
if (event.tool === 'readFileTool' && event.event === 'complete') {
|
||||
onMessage({
|
||||
message: {
|
||||
kind: 'read',
|
||||
event: 'complete',
|
||||
args: event.args,
|
||||
result: event.result, // Type-safe: ReadFileToolOutput
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
|
|
|
@ -74,6 +74,7 @@ export function createAnalyticsEngineerAgent(analyticsEngineerAgentOptions: Anal
|
|||
const readFileTool = createReadFileTool({
|
||||
messageId: analyticsEngineerAgentOptions.messageId,
|
||||
projectDirectory: analyticsEngineerAgentOptions.folder_structure,
|
||||
onToolEvent: analyticsEngineerAgentOptions.onToolEvent,
|
||||
});
|
||||
const bashTool = createBashTool({
|
||||
messageId: analyticsEngineerAgentOptions.messageId,
|
||||
|
|
|
@ -34,11 +34,18 @@ function validateFilePath(filePath: string, projectDirectory: string): void {
|
|||
*/
|
||||
export function createReadFileToolExecute(context: ReadFileToolContext) {
|
||||
return async function execute(input: ReadFileToolInput): Promise<ReadFileToolOutput> {
|
||||
const { messageId, projectDirectory } = context;
|
||||
const { messageId, projectDirectory, onToolEvent } = context;
|
||||
const { filePath } = input;
|
||||
|
||||
console.info(`Reading file ${filePath} for message ${messageId}`);
|
||||
|
||||
// Emit start event
|
||||
onToolEvent?.({
|
||||
tool: 'readFileTool',
|
||||
event: 'start',
|
||||
args: input,
|
||||
});
|
||||
|
||||
try {
|
||||
// Convert to absolute path if relative
|
||||
const absolutePath = path.isAbsolute(filePath)
|
||||
|
@ -69,21 +76,41 @@ export function createReadFileToolExecute(context: ReadFileToolContext) {
|
|||
|
||||
console.info(`Successfully read file: ${filePath}`);
|
||||
|
||||
return {
|
||||
status: 'success',
|
||||
const output = {
|
||||
status: 'success' as const,
|
||||
file_path: filePath,
|
||||
content: finalContent,
|
||||
truncated,
|
||||
};
|
||||
|
||||
// Emit complete event
|
||||
onToolEvent?.({
|
||||
tool: 'readFileTool',
|
||||
event: 'complete',
|
||||
result: output,
|
||||
args: input,
|
||||
});
|
||||
|
||||
return output;
|
||||
} catch (error) {
|
||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||
console.error(`Error reading file ${filePath}:`, errorMessage);
|
||||
|
||||
return {
|
||||
status: 'error',
|
||||
const output = {
|
||||
status: 'error' as const,
|
||||
file_path: filePath,
|
||||
error_message: errorMessage,
|
||||
};
|
||||
|
||||
// Emit complete event even on error
|
||||
onToolEvent?.({
|
||||
tool: 'readFileTool',
|
||||
event: 'complete',
|
||||
result: output,
|
||||
args: input,
|
||||
});
|
||||
|
||||
return output;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ const ReadFileToolOutputSchema = z.discriminatedUnion('status', [
|
|||
const ReadFileToolContextSchema = z.object({
|
||||
messageId: z.string().describe('The message ID for database updates'),
|
||||
projectDirectory: z.string().describe('The root directory of the project'),
|
||||
onToolEvent: z.any().optional().describe('Callback for tool events'),
|
||||
});
|
||||
|
||||
export type ReadFileToolInput = z.infer<typeof ReadFileToolInputSchema>;
|
||||
|
|
Loading…
Reference in New Issue