rearrange and get some things right for the initial read file tool

This commit is contained in:
dal 2025-07-20 23:04:11 -06:00
parent 7e1adbec2a
commit 06249f5e8a
No known key found for this signature in database
GPG Key ID: 16F4B0E1E9F61122
10 changed files with 44 additions and 149 deletions

View File

@ -330,27 +330,27 @@ export const slackAgentTask: ReturnType<
let progressMessageTs: string | undefined;
let queuedMessageTs: string | undefined;
let hasStartedRunning = false;
// First, do rapid polling for up to 20 seconds to see if task starts
const rapidPollInterval = 1000; // 1 second
const maxRapidPolls = 20; // 20 attempts = 20 seconds total
let rapidPollCount = 0;
let isComplete = false;
let analystResult: { ok: boolean; output?: unknown; error?: unknown } | null = null;
while (rapidPollCount < maxRapidPolls && !hasStartedRunning && !isComplete) {
await wait.for({ seconds: rapidPollInterval / 1000 });
rapidPollCount++;
try {
const run = await runs.retrieve(analystHandle.id);
logger.log('Rapid polling analyst task status', {
runId: analystHandle.id,
status: run.status,
pollCount: rapidPollCount,
});
// Check if task has started
if (run.status === 'EXECUTING' || run.status === 'REATTEMPTING') {
hasStartedRunning = true;
@ -358,7 +358,7 @@ export const slackAgentTask: ReturnType<
runId: analystHandle.id,
pollCount: rapidPollCount,
});
// Send the progress message
try {
const progressMessage = {
@ -397,7 +397,9 @@ export const slackAgentTask: ReturnType<
if (sendResult.success && sendResult.messageTs) {
progressMessageTs = sendResult.messageTs;
logger.log('Sent progress message to Slack thread', { messageTs: progressMessageTs });
logger.log('Sent progress message to Slack thread', {
messageTs: progressMessageTs,
});
}
} catch (error) {
logger.warn('Failed to send progress message to Slack', {
@ -454,7 +456,7 @@ export const slackAgentTask: ReturnType<
});
}
}
// Step 7: Main polling loop for task completion
const maxPollingTime = 30 * 60 * 1000; // 30 minutes
const normalPollingInterval = 10000; // 10 seconds
@ -542,7 +544,9 @@ export const slackAgentTask: ReturnType<
if (sendResult.success && sendResult.messageTs) {
progressMessageTs = sendResult.messageTs;
logger.log('Sent progress message to Slack thread', { messageTs: progressMessageTs });
logger.log('Sent progress message to Slack thread', {
messageTs: progressMessageTs,
});
}
} catch (error) {
logger.warn('Failed to send progress message to Slack', {

View File

@ -0,0 +1,9 @@
import type Sandbox from '@daytonaio/sdk';
export enum SandboxContextKey {
Sandbox = 'sandbox',
}
export type SandboxContext = {
sandbox: Sandbox;
};

View File

@ -11,9 +11,7 @@ export interface FileReadResult {
async function readSingleFile(filePath: string): Promise<FileReadResult> {
try {
const resolvedPath = path.isAbsolute(filePath)
? filePath
: path.join(process.cwd(), filePath);
const resolvedPath = path.isAbsolute(filePath) ? filePath : path.join(process.cwd(), filePath);
try {
await fs.access(resolvedPath);
@ -36,14 +34,14 @@ async function readSingleFile(filePath: string): Promise<FileReadResult> {
content: truncatedContent,
truncated: true,
};
} else {
return {
success: true,
filePath,
content,
truncated: false,
};
}
return {
success: true,
filePath,
content,
truncated: false,
};
} catch (error) {
return {
success: false,
@ -54,6 +52,6 @@ async function readSingleFile(filePath: string): Promise<FileReadResult> {
}
export async function readFilesSafely(filePaths: string[]): Promise<FileReadResult[]> {
const fileReadPromises = filePaths.map(filePath => readSingleFile(filePath));
const fileReadPromises = filePaths.map((filePath) => readSingleFile(filePath));
return Promise.all(fileReadPromises);
}

View File

@ -2,7 +2,8 @@ import type { RuntimeContext } from '@mastra/core/runtime-context';
import { createTool } from '@mastra/core/tools';
import { wrapTraced } from 'braintrust';
import { z } from 'zod';
import type { AnalystRuntimeContext } from '../../schemas/workflow-schemas';
import type { SandboxContext } from '../../../context/sandbox-context';
import type { AnalystRuntimeContext } from '../../../schemas/workflow-schemas';
const readFilesInputSchema = z.object({
files: z
@ -32,60 +33,10 @@ const readFilesOutputSchema = z.object({
),
});
export function parseStreamingArgs(
accumulatedText: string
): Partial<z.infer<typeof readFilesInputSchema>> | null {
if (typeof accumulatedText !== 'string') {
throw new Error(`parseStreamingArgs expects string input, got ${typeof accumulatedText}`);
}
try {
const parsed = JSON.parse(accumulatedText);
if (parsed.files !== undefined && !Array.isArray(parsed.files)) {
console.warn('[read-files parseStreamingArgs] files is not an array:', {
type: typeof parsed.files,
value: parsed.files,
});
return null;
}
return { files: parsed.files || undefined };
} catch (error) {
if (error instanceof SyntaxError) {
const filesMatch = accumulatedText.match(/"files"\s*:\s*\[(.*)/s);
if (filesMatch && filesMatch[1] !== undefined) {
const arrayContent = filesMatch[1];
try {
const testArray = `[${arrayContent}]`;
const parsed = JSON.parse(testArray);
return { files: parsed };
} catch {
const files: string[] = [];
const fileMatches = arrayContent.matchAll(/"((?:[^"\\]|\\.)*)"/g);
for (const match of fileMatches) {
if (match[1] !== undefined) {
const filePath = match[1].replace(/\\"/g, '"').replace(/\\\\/g, '\\');
files.push(filePath);
}
}
return { files };
}
}
const partialMatch = accumulatedText.match(/"files"\s*:\s*\[/);
if (partialMatch) {
return { files: [] };
}
return null;
}
throw new Error(
`Unexpected error in parseStreamingArgs: ${error instanceof Error ? error.message : 'Unknown error'}`
);
}
}
const readFilesExecution = wrapTraced(
async (
params: z.infer<typeof readFilesInputSchema>,
_runtimeContext: RuntimeContext<AnalystRuntimeContext>
runtimeContext: RuntimeContext<SandboxContext>
): Promise<z.infer<typeof readFilesOutputSchema>> => {
const { files } = params;
@ -94,7 +45,7 @@ const readFilesExecution = wrapTraced(
}
try {
const { readFilesSafely } = await import('./file-operations');
const { readFilesSafely } = await import('./read-file');
const fileResults = await readFilesSafely(files);
return {
@ -137,7 +88,7 @@ export const readFiles = createTool({
runtimeContext,
}: {
context: z.infer<typeof readFilesInputSchema>;
runtimeContext: RuntimeContext<AnalystRuntimeContext>;
runtimeContext: RuntimeContext<SandboxContext>;
}) => {
return await readFilesExecution(context, runtimeContext);
},

View File

@ -0,0 +1 @@
// This file is intentionally empty as all tests were related to the removed parseStreamingArgs function

View File

@ -1,72 +0,0 @@
import { describe, expect, test } from 'vitest';
import { parseStreamingArgs } from './read-files';
describe('Read Files Tool Streaming Parser', () => {
test('should return null for empty or invalid input', () => {
expect(parseStreamingArgs('')).toBe(null);
expect(parseStreamingArgs('{')).toBe(null);
expect(parseStreamingArgs('invalid json')).toBe(null);
});
test('should parse complete JSON with files array', () => {
const input = '{"files": ["/path/to/file1.txt", "./relative/file2.ts"]}';
const result = parseStreamingArgs(input);
expect(result).toEqual({
files: ['/path/to/file1.txt', './relative/file2.ts'],
});
});
test('should handle empty files array', () => {
const input = '{"files": []}';
const result = parseStreamingArgs(input);
expect(result).toEqual({ files: [] });
});
test('should extract partial files array', () => {
const input = '{"files": ["/path/to/file1.txt"';
const result = parseStreamingArgs(input);
expect(result).toEqual({ files: ['/path/to/file1.txt'] });
});
test('should handle files field start without content', () => {
const input = '{"files": [';
const result = parseStreamingArgs(input);
expect(result).toEqual({ files: [] });
});
test('should return null for non-array files field', () => {
const input = '{"files": "not an array"}';
const result = parseStreamingArgs(input);
expect(result).toBe(null);
});
test('should handle escaped quotes in file paths', () => {
const input = '{"files": ["/path/to/\\"quoted\\"/file.txt"]}';
const result = parseStreamingArgs(input);
expect(result).toEqual({
files: ['/path/to/"quoted"/file.txt'],
});
});
test('should extract multiple files from partial JSON', () => {
const input = '{"files": ["/file1.txt", "/file2.txt", "/file3.txt"';
const result = parseStreamingArgs(input);
expect(result).toEqual({
files: ['/file1.txt', '/file2.txt', '/file3.txt'],
});
});
test('should throw error for non-string input', () => {
expect(() => parseStreamingArgs(123 as any)).toThrow(
'parseStreamingArgs expects string input, got number'
);
});
test('should handle mixed absolute and relative paths', () => {
const input = '{"files": ["/absolute/path.txt", "./relative/path.ts", "../parent/file.js"]}';
const result = parseStreamingArgs(input);
expect(result).toEqual({
files: ['/absolute/path.txt', './relative/path.ts', '../parent/file.js'],
});
});
});

View File

@ -9,4 +9,4 @@ export { createDashboards } from './visualization-tools/create-dashboards-file-t
export { modifyDashboards } from './visualization-tools/modify-dashboards-file-tool';
export { executeSql } from './database-tools/execute-sql';
export { createTodoList } from './planning-thinking-tools/create-todo-item-tool';
export { readFiles } from './file-tools/read-files';
export { readFiles } from './file-tools/read-files-tool/read-files-tool';

View File

@ -29,7 +29,7 @@
"dependencies": {
"@buster/typescript-config": "workspace:*",
"@buster/vitest-config": "workspace:*",
"@daytonaio/sdk": "^0.24.2",
"@daytonaio/sdk": "catalog:",
"zod": "catalog:"
}
}

View File

@ -6,6 +6,9 @@ settings:
catalogs:
default:
'@daytonaio/sdk':
specifier: ^0.24.2
version: 0.24.2
'@mastra/core':
specifier: ^0.10.8
version: 0.10.8
@ -812,7 +815,7 @@ importers:
specifier: workspace:*
version: link:../vitest-config
'@daytonaio/sdk':
specifier: ^0.24.2
specifier: 'catalog:'
version: 0.24.2
zod:
specifier: 'catalog:'

View File

@ -17,6 +17,7 @@ catalog:
ai: "^4.0.0"
axios: "^1.10.0"
"braintrust": "^0.0.209"
"@daytonaio/sdk": "^0.24.2"
drizzle-orm: "^0.44.2"
hono: "^4.8.0"
pg: "^8.16.2"