@@ -206,6 +206,119 @@ async function maybeWriteOutputToTable(
206206 }
207207}
208208
209+ async function maybeWriteReadCsvToTable (
210+ toolName : string ,
211+ params : Record < string , unknown > | undefined ,
212+ result : ToolCallResult ,
213+ context : ExecutionContext
214+ ) : Promise < ToolCallResult > {
215+ if ( toolName !== 'read' ) return result
216+ if ( ! result . success || ! result . output ) return result
217+ if ( ! context . workspaceId || ! context . userId ) return result
218+
219+ const outputTable = params ?. outputTable as string | undefined
220+ if ( ! outputTable ) return result
221+
222+ try {
223+ const table = await getTableById ( outputTable )
224+ if ( ! table ) {
225+ return { success : false , error : `Table "${ outputTable } " not found` }
226+ }
227+
228+ const output = result . output as Record < string , unknown >
229+ const content = ( output . content as string ) || ''
230+ if ( ! content . trim ( ) ) {
231+ return { success : false , error : 'File has no content to import into table' }
232+ }
233+
234+ const filePath = ( params ?. path as string ) || ''
235+ const ext = filePath . split ( '.' ) . pop ( ) ?. toLowerCase ( )
236+
237+ let rows : Record < string , unknown > [ ]
238+
239+ if ( ext === 'json' ) {
240+ const parsed = JSON . parse ( content )
241+ if ( ! Array . isArray ( parsed ) ) {
242+ return {
243+ success : false ,
244+ error : 'JSON file must contain an array of objects for table import' ,
245+ }
246+ }
247+ rows = parsed
248+ } else {
249+ const { parse } = await import ( 'csv-parse/sync' )
250+ rows = parse ( content , {
251+ columns : true ,
252+ skip_empty_lines : true ,
253+ trim : true ,
254+ relax_column_count : true ,
255+ relax_quotes : true ,
256+ skip_records_with_error : true ,
257+ cast : false ,
258+ } ) as Record < string , unknown > [ ]
259+ }
260+
261+ if ( rows . length === 0 ) {
262+ return { success : false , error : 'File has no data rows to import' }
263+ }
264+
265+ if ( rows . length > MAX_OUTPUT_TABLE_ROWS ) {
266+ return {
267+ success : false ,
268+ error : `Row limit exceeded: got ${ rows . length } , max is ${ MAX_OUTPUT_TABLE_ROWS } ` ,
269+ }
270+ }
271+
272+ await db . transaction ( async ( tx ) => {
273+ await tx . delete ( userTableRows ) . where ( eq ( userTableRows . tableId , outputTable ) )
274+
275+ const now = new Date ( )
276+ for ( let i = 0 ; i < rows . length ; i += BATCH_CHUNK_SIZE ) {
277+ const chunk = rows . slice ( i , i + BATCH_CHUNK_SIZE )
278+ const values = chunk . map ( ( rowData , j ) => ( {
279+ id : `row_${ crypto . randomUUID ( ) . replace ( / - / g, '' ) } ` ,
280+ tableId : outputTable ,
281+ workspaceId : context . workspaceId ! ,
282+ data : rowData ,
283+ position : i + j ,
284+ createdAt : now ,
285+ updatedAt : now ,
286+ createdBy : context . userId ,
287+ } ) )
288+ await tx . insert ( userTableRows ) . values ( values )
289+ }
290+ } )
291+
292+ logger . info ( 'Read output written to table' , {
293+ toolName,
294+ tableId : outputTable ,
295+ tableName : table . name ,
296+ rowCount : rows . length ,
297+ filePath,
298+ } )
299+
300+ return {
301+ success : true ,
302+ output : {
303+ message : `Imported ${ rows . length } rows from "${ filePath } " into table "${ table . name } "` ,
304+ tableId : outputTable ,
305+ tableName : table . name ,
306+ rowCount : rows . length ,
307+ } ,
308+ }
309+ } catch ( err ) {
310+ logger . warn ( 'Failed to write read output to table' , {
311+ toolName,
312+ outputTable,
313+ error : err instanceof Error ? err . message : String ( err ) ,
314+ } )
315+ return {
316+ success : false ,
317+ error : `Failed to import into table: ${ err instanceof Error ? err . message : String ( err ) } ` ,
318+ }
319+ }
320+ }
321+
209322export async function executeToolAndReport (
210323 toolCallId : string ,
211324 context : StreamingContext ,
@@ -230,6 +343,7 @@ export async function executeToolAndReport(
230343 let result = await executeToolServerSide ( toolCall , execContext )
231344 result = await maybeWriteOutputToFile ( toolCall . name , toolCall . params , result , execContext )
232345 result = await maybeWriteOutputToTable ( toolCall . name , toolCall . params , result , execContext )
346+ result = await maybeWriteReadCsvToTable ( toolCall . name , toolCall . params , result , execContext )
233347 toolCall . status = result . success ? 'success' : 'error'
234348 toolCall . result = result
235349 toolCall . error = result . error
0 commit comments