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 { ExecuteMessage } from './execute-message';
|
||||||
import { WriteMessage } from './write-message';
|
import { WriteMessage } from './write-message';
|
||||||
import { EditMessage } from './edit-message';
|
import { EditMessage } from './edit-message';
|
||||||
|
import { ReadMessage } from './read-message';
|
||||||
|
|
||||||
interface AgentMessageComponentProps {
|
interface AgentMessageComponentProps {
|
||||||
message: AgentMessage;
|
message: AgentMessage;
|
||||||
|
@ -52,6 +53,10 @@ export function AgentMessageComponent({ message }: AgentMessageComponentProps) {
|
||||||
// For edit operations, use the EditMessage component
|
// For edit operations, use the EditMessage component
|
||||||
return <EditMessage message={message} />;
|
return <EditMessage message={message} />;
|
||||||
|
|
||||||
|
case 'read':
|
||||||
|
// For read operations, use the ReadMessage component
|
||||||
|
return <ReadMessage message={message} />;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
|
@ -48,6 +48,12 @@ export type AgentMessage =
|
||||||
event: 'start' | 'complete';
|
event: 'start' | 'complete';
|
||||||
args: { filePath: string; oldString?: string; newString?: string; edits?: Array<{ oldString: string; newString: string }> };
|
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 };
|
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 {
|
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({
|
const readFileTool = createReadFileTool({
|
||||||
messageId: analyticsEngineerAgentOptions.messageId,
|
messageId: analyticsEngineerAgentOptions.messageId,
|
||||||
projectDirectory: analyticsEngineerAgentOptions.folder_structure,
|
projectDirectory: analyticsEngineerAgentOptions.folder_structure,
|
||||||
|
onToolEvent: analyticsEngineerAgentOptions.onToolEvent,
|
||||||
});
|
});
|
||||||
const bashTool = createBashTool({
|
const bashTool = createBashTool({
|
||||||
messageId: analyticsEngineerAgentOptions.messageId,
|
messageId: analyticsEngineerAgentOptions.messageId,
|
||||||
|
|
|
@ -34,11 +34,18 @@ function validateFilePath(filePath: string, projectDirectory: string): void {
|
||||||
*/
|
*/
|
||||||
export function createReadFileToolExecute(context: ReadFileToolContext) {
|
export function createReadFileToolExecute(context: ReadFileToolContext) {
|
||||||
return async function execute(input: ReadFileToolInput): Promise<ReadFileToolOutput> {
|
return async function execute(input: ReadFileToolInput): Promise<ReadFileToolOutput> {
|
||||||
const { messageId, projectDirectory } = context;
|
const { messageId, projectDirectory, onToolEvent } = context;
|
||||||
const { filePath } = input;
|
const { filePath } = input;
|
||||||
|
|
||||||
console.info(`Reading file ${filePath} for message ${messageId}`);
|
console.info(`Reading file ${filePath} for message ${messageId}`);
|
||||||
|
|
||||||
|
// Emit start event
|
||||||
|
onToolEvent?.({
|
||||||
|
tool: 'readFileTool',
|
||||||
|
event: 'start',
|
||||||
|
args: input,
|
||||||
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Convert to absolute path if relative
|
// Convert to absolute path if relative
|
||||||
const absolutePath = path.isAbsolute(filePath)
|
const absolutePath = path.isAbsolute(filePath)
|
||||||
|
@ -69,21 +76,41 @@ export function createReadFileToolExecute(context: ReadFileToolContext) {
|
||||||
|
|
||||||
console.info(`Successfully read file: ${filePath}`);
|
console.info(`Successfully read file: ${filePath}`);
|
||||||
|
|
||||||
return {
|
const output = {
|
||||||
status: 'success',
|
status: 'success' as const,
|
||||||
file_path: filePath,
|
file_path: filePath,
|
||||||
content: finalContent,
|
content: finalContent,
|
||||||
truncated,
|
truncated,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Emit complete event
|
||||||
|
onToolEvent?.({
|
||||||
|
tool: 'readFileTool',
|
||||||
|
event: 'complete',
|
||||||
|
result: output,
|
||||||
|
args: input,
|
||||||
|
});
|
||||||
|
|
||||||
|
return output;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
||||||
console.error(`Error reading file ${filePath}:`, errorMessage);
|
console.error(`Error reading file ${filePath}:`, errorMessage);
|
||||||
|
|
||||||
return {
|
const output = {
|
||||||
status: 'error',
|
status: 'error' as const,
|
||||||
file_path: filePath,
|
file_path: filePath,
|
||||||
error_message: errorMessage,
|
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({
|
const ReadFileToolContextSchema = z.object({
|
||||||
messageId: z.string().describe('The message ID for database updates'),
|
messageId: z.string().describe('The message ID for database updates'),
|
||||||
projectDirectory: z.string().describe('The root directory of the project'),
|
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>;
|
export type ReadFileToolInput = z.infer<typeof ReadFileToolInputSchema>;
|
||||||
|
|
Loading…
Reference in New Issue