feat(router): implement file_tool excel actions on NODE1 stack

This commit is contained in:
Apple
2026-02-15 02:11:28 -08:00
parent 21576f0ca3
commit e91584246d
2 changed files with 137 additions and 0 deletions

View File

@@ -48,6 +48,8 @@ Critical files were drifted against NODE1 runtime (15 files):
Implemented in actual NODE1 stack (`services/router/*` + gateway):
### Added actions
- `excel_create`
- `excel_update`
- `csv_create`
- `csv_update`
- `json_export`
@@ -90,6 +92,7 @@ For file-producing tool calls, router now propagates:
## Smoke Tests
Run inside `dagi-router-node1` to validate actions deterministically:
- Excel create/update
- CSV create/update
- JSON/YAML export
- ZIP bundle
@@ -104,6 +107,7 @@ Also verify infer endpoint still works:
- `rollback_backups/file_tool_step2_tool_manager.py.bak_20260215_012029`
- `rollback_backups/file_tool_step3_tool_manager.py.bak_20260215_012200`
- `rollback_backups/file_tool_step4_tool_manager.py.bak_20260215_012309`
- `services/router/tool_manager.py.bak_20260215_020902`
## Rollback (NODE1)
```bash

View File

@@ -588,11 +588,22 @@ class ToolManager:
out.append([row])
return out
@staticmethod
def _append_sheet_data(ws: Any, headers: List[str], rows: List[List[Any]]) -> None:
if headers:
ws.append(headers)
for row in rows:
ws.append(row)
async def _file_tool(self, args: Dict[str, Any]) -> ToolResult:
action = str((args or {}).get("action") or "").strip().lower()
if not action:
return ToolResult(success=False, result=None, error="Missing action")
if action == "excel_create":
return self._file_excel_create(args)
if action == "excel_update":
return self._file_excel_update(args)
if action == "csv_create":
return self._file_csv_create(args)
if action == "csv_update":
@@ -751,6 +762,128 @@ class ToolManager:
file_mime="application/zip",
)
def _file_excel_create(self, args: Dict[str, Any]) -> ToolResult:
import openpyxl
file_name = self._sanitize_file_name(args.get("file_name"), "report.xlsx", force_ext=".xlsx")
sheets = args.get("sheets")
wb = openpyxl.Workbook()
wb.remove(wb.active)
created = False
if isinstance(sheets, list) and sheets:
for idx, sheet in enumerate(sheets, start=1):
if not isinstance(sheet, dict):
return ToolResult(success=False, result=None, error=f"sheets[{idx-1}] must be object")
sheet_name = str(sheet.get("name") or f"Sheet{idx}")[:31]
headers = sheet.get("headers") or []
rows_raw = sheet.get("rows") or []
rows = self._normalize_rows(rows_raw, headers=headers if headers else None)
if rows and not headers and isinstance(rows_raw[0], dict):
headers = list(rows_raw[0].keys())
rows = self._normalize_rows(rows_raw, headers=headers)
ws = wb.create_sheet(title=sheet_name)
self._append_sheet_data(ws, headers, rows)
created = True
else:
sheet_name = str(args.get("sheet_name") or "Sheet1")[:31]
headers = args.get("headers") or []
rows_raw = args.get("rows") or []
rows = self._normalize_rows(rows_raw, headers=headers if headers else None)
if rows and not headers and isinstance(rows_raw[0], dict):
headers = list(rows_raw[0].keys())
rows = self._normalize_rows(rows_raw, headers=headers)
ws = wb.create_sheet(title=sheet_name)
self._append_sheet_data(ws, headers, rows)
created = True
if not created:
wb.create_sheet(title="Sheet1")
out = BytesIO()
wb.save(out)
return ToolResult(
success=True,
result={"message": f"Excel created: {file_name}"},
file_base64=self._b64_from_bytes(out.getvalue()),
file_name=file_name,
file_mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
def _file_excel_update(self, args: Dict[str, Any]) -> ToolResult:
import openpyxl
src_b64 = args.get("file_base64")
operations = args.get("operations") or []
if not src_b64:
return ToolResult(success=False, result=None, error="file_base64 is required for excel_update")
if not isinstance(operations, list) or not operations:
return ToolResult(success=False, result=None, error="operations must be non-empty array")
file_name = self._sanitize_file_name(args.get("file_name"), "updated.xlsx", force_ext=".xlsx")
wb = openpyxl.load_workbook(filename=BytesIO(self._bytes_from_b64(src_b64)))
for op in operations:
if not isinstance(op, dict):
return ToolResult(success=False, result=None, error="Each operation must be object")
op_type = str(op.get("type") or "").strip().lower()
if op_type == "append_rows":
sheet = str(op.get("sheet") or wb.sheetnames[0])[:31]
if sheet not in wb.sheetnames:
wb.create_sheet(title=sheet)
ws = wb[sheet]
rows_raw = op.get("rows") or []
header_row = [c.value for c in ws[1]] if ws.max_row >= 1 else []
rows = self._normalize_rows(rows_raw, headers=header_row if header_row else None)
if rows and not header_row and isinstance(rows_raw[0], dict):
header_row = list(rows_raw[0].keys())
ws.append(header_row)
rows = self._normalize_rows(rows_raw, headers=header_row)
for row in rows:
ws.append(row)
elif op_type == "set_cell":
sheet = str(op.get("sheet") or wb.sheetnames[0])[:31]
cell = op.get("cell")
if not cell:
return ToolResult(success=False, result=None, error="set_cell operation requires 'cell'")
if sheet not in wb.sheetnames:
wb.create_sheet(title=sheet)
wb[sheet][str(cell)] = op.get("value", "")
elif op_type == "replace_sheet":
sheet = str(op.get("sheet") or wb.sheetnames[0])[:31]
if sheet in wb.sheetnames:
wb.remove(wb[sheet])
ws = wb.create_sheet(title=sheet)
headers = op.get("headers") or []
rows_raw = op.get("rows") or []
rows = self._normalize_rows(rows_raw, headers=headers if headers else None)
if rows and not headers and isinstance(rows_raw[0], dict):
headers = list(rows_raw[0].keys())
rows = self._normalize_rows(rows_raw, headers=headers)
self._append_sheet_data(ws, headers, rows)
elif op_type == "rename_sheet":
src = str(op.get("from") or "")
dst = str(op.get("to") or "").strip()
if not src or not dst:
return ToolResult(success=False, result=None, error="rename_sheet requires 'from' and 'to'")
if src not in wb.sheetnames:
return ToolResult(success=False, result=None, error=f"Sheet not found: {src}")
wb[src].title = dst[:31]
else:
return ToolResult(success=False, result=None, error=f"Unsupported excel_update operation: {op_type}")
out = BytesIO()
wb.save(out)
return ToolResult(
success=True,
result={"message": f"Excel updated: {file_name}"},
file_base64=self._b64_from_bytes(out.getvalue()),
file_name=file_name,
file_mime="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
)
def _file_docx_create(self, args: Dict[str, Any]) -> ToolResult:
from docx import Document