read file messages

This commit is contained in:
dal 2025-10-03 09:20:29 -06:00
parent 775b09b6a3
commit bd195df18f
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
6 changed files with 154 additions and 5 deletions

View File

@ -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>
);
}

View File

@ -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;
}

View File

@ -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
},
});
}
},
});

View File

@ -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,

View File

@ -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;
}
};
}

View File

@ -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>;