(payload: LocalToolCreateRequest)
| 42 | |
| 43 | @router.post("/api/tools/local") |
| 44 | def create_local_tool(payload: LocalToolCreateRequest): |
| 45 | raw_name = payload.filename.strip() |
| 46 | if not raw_name: |
| 47 | raise HTTPException(status_code=400, detail="filename is required") |
| 48 | |
| 49 | if not re.match(r"^[A-Za-z0-9_-]+(\.py)?$", raw_name): |
| 50 | raise HTTPException(status_code=400, detail="filename must be alphanumeric with optional .py extension") |
| 51 | |
| 52 | filename = raw_name if raw_name.endswith(".py") else f"{raw_name}.py" |
| 53 | tools_dir = Path(FUNCTION_CALLING_DIR).resolve() |
| 54 | tools_dir.mkdir(parents=True, exist_ok=True) |
| 55 | |
| 56 | target_path = (tools_dir / filename).resolve() |
| 57 | try: |
| 58 | target_path.relative_to(tools_dir) |
| 59 | except ValueError: |
| 60 | raise HTTPException(status_code=400, detail="filename resolves outside function tools directory") |
| 61 | |
| 62 | if target_path.exists() and not payload.overwrite: |
| 63 | raise HTTPException(status_code=409, detail="tool file already exists") |
| 64 | |
| 65 | target_path.write_text(payload.content, encoding="utf-8") |
| 66 | |
| 67 | catalog = get_function_catalog() |
| 68 | catalog.refresh() |
| 69 | return { |
| 70 | "success": True, |
| 71 | "filename": filename, |
| 72 | "path": str(target_path), |
| 73 | "load_error": str(catalog.load_error) if catalog.load_error else None, |
| 74 | } |
nothing calls this directly
no test coverage detected