( toolName: string, params: Record<string, unknown> | undefined, result: ToolCallResult, context: ExecutionContext )
| 170 | } |
| 171 | |
| 172 | export async function maybeWriteReadCsvToTable( |
| 173 | toolName: string, |
| 174 | params: Record<string, unknown> | undefined, |
| 175 | result: ToolCallResult, |
| 176 | context: ExecutionContext |
| 177 | ): Promise<ToolCallResult> { |
| 178 | if (toolName !== ReadTool.id) return result |
| 179 | if (!result.success || !result.output) return result |
| 180 | if (!context.workspaceId || !context.userId) return result |
| 181 | |
| 182 | const outputTable = params?.outputTable as string | undefined |
| 183 | if (!outputTable) return result |
| 184 | |
| 185 | const denied = denyOutputWriteWithoutWritePermission(context) |
| 186 | if (denied) return denied |
| 187 | |
| 188 | return withCopilotSpan( |
| 189 | TraceSpan.CopilotToolsWriteCsvToTable, |
| 190 | { |
| 191 | [TraceAttr.ToolName]: toolName, |
| 192 | [TraceAttr.CopilotTableId]: outputTable, |
| 193 | [TraceAttr.WorkspaceId]: context.workspaceId, |
| 194 | }, |
| 195 | async (span) => { |
| 196 | try { |
| 197 | const table = await getTableById(outputTable) |
| 198 | if (!table || table.workspaceId !== context.workspaceId) { |
| 199 | span.setAttribute(TraceAttr.CopilotTableOutcome, CopilotTableOutcome.TableNotFound) |
| 200 | return { success: false, error: `Table "${outputTable}" not found` } |
| 201 | } |
| 202 | |
| 203 | const output = result.output as Record<string, unknown> |
| 204 | const content = (output.content as string) || '' |
| 205 | if (!content.trim()) { |
| 206 | span.setAttribute(TraceAttr.CopilotTableOutcome, CopilotTableOutcome.EmptyContent) |
| 207 | return { success: false, error: 'File has no content to import into table' } |
| 208 | } |
| 209 | |
| 210 | const filePath = (params?.path as string) || '' |
| 211 | const ext = filePath.split('.').pop()?.toLowerCase() |
| 212 | span.setAttributes({ |
| 213 | [TraceAttr.CopilotTableSourcePath]: filePath, |
| 214 | [TraceAttr.CopilotTableSourceFormat]: ext === 'json' ? 'json' : 'csv', |
| 215 | [TraceAttr.CopilotTableSourceContentBytes]: content.length, |
| 216 | }) |
| 217 | |
| 218 | let rows: Record<string, unknown>[] |
| 219 | |
| 220 | if (ext === 'json') { |
| 221 | const parsed = JSON.parse(content) |
| 222 | if (!Array.isArray(parsed)) { |
| 223 | span.setAttribute(TraceAttr.CopilotTableOutcome, CopilotTableOutcome.InvalidJsonShape) |
| 224 | return { |
| 225 | success: false, |
| 226 | error: 'JSON file must contain an array of objects for table import', |
| 227 | } |
| 228 | } |
| 229 | rows = parsed |
no test coverage detected