Safetensors
GGUF
Turkish
llama
Llama-3
instruct
finetune
chatml
gpt4
synthetic data
distillation
function calling
json mode
axolotl
roleplaying
chat
Instructions to use tda45/TdAI with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- llama-cpp-python
How to use tda45/TdAI with llama-cpp-python:
# !pip install llama-cpp-python from llama_cpp import Llama llm = Llama.from_pretrained( repo_id="tda45/TdAI", filename="llama.cpp/models/ggml-vocab-aquila.gguf", )
output = llm( "Once upon a time,", max_tokens=512, echo=True ) print(output)
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use tda45/TdAI with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf tda45/TdAI # Run inference directly in the terminal: llama cli -hf tda45/TdAI
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./llama-cli -hf tda45/TdAI
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf tda45/TdAI # Run inference directly in the terminal: ./build/bin/llama-cli -hf tda45/TdAI
Use Docker
docker model run hf.co/tda45/TdAI
- LM Studio
- Jan
- Ollama
How to use tda45/TdAI with Ollama:
ollama run hf.co/tda45/TdAI
- Unsloth Studio
How to use tda45/TdAI with Unsloth Studio:
Install Unsloth Studio (macOS, Linux, WSL)
curl -fsSL https://unsloth.ai/install.sh | sh # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Install Unsloth Studio (Windows)
irm https://unsloth.ai/install.ps1 | iex # Run unsloth studio unsloth studio -H 0.0.0.0 -p 8888 # Then open http://localhost:8888 in your browser # Search for tda45/TdAI to start chatting
Using HuggingFace Spaces for Unsloth
# No setup required # Open https://huggingface.co/spaces/unsloth/studio in your browser # Search for tda45/TdAI to start chatting
- Atomic Chat new
- Docker Model Runner
How to use tda45/TdAI with Docker Model Runner:
docker model run hf.co/tda45/TdAI
- Lemonade
How to use tda45/TdAI with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull tda45/TdAI
Run and chat with the model
lemonade run user.TdAI-{{QUANT_TAG}}List all available models
lemonade list
| namespace fs = std::filesystem; | |
| // | |
| // internal helpers | |
| // | |
| static std::vector<char *> to_cstr_vec(const std::vector<std::string> & v) { | |
| std::vector<char *> r; | |
| r.reserve(v.size() + 1); | |
| for (const auto & s : v) { | |
| r.push_back(const_cast<char *>(s.c_str())); | |
| } | |
| r.push_back(nullptr); | |
| return r; | |
| } | |
| struct run_proc_result { | |
| std::string output; | |
| int exit_code = -1; | |
| bool timed_out = false; | |
| }; | |
| static run_proc_result run_process( | |
| const std::vector<std::string> & args, | |
| size_t max_output, | |
| int timeout_secs) { | |
| run_proc_result res; | |
| subprocess_s proc; | |
| auto argv = to_cstr_vec(args); | |
| int options = subprocess_option_no_window | |
| | subprocess_option_combined_stdout_stderr | |
| | subprocess_option_inherit_environment | |
| | subprocess_option_search_user_path; | |
| if (subprocess_create(argv.data(), options, &proc) != 0) { | |
| res.output = "failed to spawn process"; | |
| return res; | |
| } | |
| std::atomic<bool> done{false}; | |
| std::atomic<bool> timed_out{false}; | |
| std::thread timeout_thread([&]() { | |
| auto deadline = std::chrono::steady_clock::now() + std::chrono::seconds(timeout_secs); | |
| while (!done.load()) { | |
| if (std::chrono::steady_clock::now() >= deadline) { | |
| timed_out.store(true); | |
| subprocess_terminate(&proc); | |
| return; | |
| } | |
| std::this_thread::sleep_for(std::chrono::milliseconds(100)); | |
| } | |
| }); | |
| FILE * f = subprocess_stdout(&proc); | |
| std::string output; | |
| bool truncated = false; | |
| if (f) { | |
| char buf[4096]; | |
| while (fgets(buf, sizeof(buf), f) != nullptr) { | |
| if (!truncated) { | |
| size_t len = strlen(buf); | |
| if (output.size() + len <= max_output) { | |
| output.append(buf, len); | |
| } else { | |
| output.append(buf, max_output - output.size()); | |
| truncated = true; | |
| } | |
| } | |
| } | |
| } | |
| done.store(true); | |
| if (timeout_thread.joinable()) { | |
| timeout_thread.join(); | |
| } | |
| subprocess_join(&proc, &res.exit_code); | |
| subprocess_destroy(&proc); | |
| res.output = output; | |
| res.timed_out = timed_out.load(); | |
| if (truncated) { | |
| res.output += "\n[output truncated]"; | |
| } | |
| return res; | |
| } | |
| json server_tool::to_json() { | |
| return { | |
| {"display_name", display_name}, | |
| {"tool", name}, | |
| {"type", "builtin"}, | |
| {"permissions", json{ | |
| {"write", permission_write} | |
| }}, | |
| {"definition", get_definition()}, | |
| }; | |
| } | |
| // | |
| // read_file: read a file with optional line range and line-number prefix | |
| // | |
| static constexpr size_t SERVER_TOOL_READ_FILE_MAX_SIZE = 16 * 1024; // 16 KB | |
| struct server_tool_read_file : server_tool { | |
| server_tool_read_file() { | |
| name = "read_file"; | |
| display_name = "Read file"; | |
| permission_write = false; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Read the contents of a file. Optionally specify a 1-based line range. " | |
| "If append_loc is true, each line is prefixed with its line number (e.g. \"1\u2192 ...\")."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"path", {{"type", "string"}, {"description", "Path to the file"}}}, | |
| {"start_line", {{"type", "integer"}, {"description", "First line to read, 1-based (default: 1)"}}}, | |
| {"end_line", {{"type", "integer"}, {"description", "Last line to read, 1-based inclusive (default: end of file)"}}}, | |
| {"append_loc", {{"type", "boolean"}, {"description", "Prefix each line with its line number"}}}, | |
| }}, | |
| {"required", json::array({"path"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string path = params.at("path").get<std::string>(); | |
| int start_line = json_value(params, "start_line", 1); | |
| int end_line = json_value(params, "end_line", -1); // -1 = no limit | |
| bool append_loc = json_value(params, "append_loc", false); | |
| std::error_code ec; | |
| uintmax_t file_size = fs::file_size(path, ec); | |
| if (ec) { | |
| return {{"error", "cannot stat file: " + ec.message()}}; | |
| } | |
| if (file_size > SERVER_TOOL_READ_FILE_MAX_SIZE && end_line == -1) { | |
| return {{"error", string_format( | |
| "file too large (%zu bytes, max %zu). Use start_line/end_line to read a portion.", | |
| (size_t)file_size, SERVER_TOOL_READ_FILE_MAX_SIZE)}}; | |
| } | |
| std::ifstream f(path); | |
| if (!f) { | |
| return {{"error", "failed to open file: " + path}}; | |
| } | |
| std::string result; | |
| std::string line; | |
| int lineno = 0; | |
| while (std::getline(f, line)) { | |
| lineno++; | |
| if (lineno < start_line) continue; | |
| if (end_line != -1 && lineno > end_line) break; | |
| std::string out_line; | |
| if (append_loc) { | |
| out_line = std::to_string(lineno) + "\u2192 " + line + "\n"; | |
| } else { | |
| out_line = line + "\n"; | |
| } | |
| if (result.size() + out_line.size() > SERVER_TOOL_READ_FILE_MAX_SIZE) { | |
| result += "[output truncated]"; | |
| break; | |
| } | |
| result += out_line; | |
| } | |
| return {{"plain_text_response", result}}; | |
| } | |
| }; | |
| // | |
| // file_glob_search: find files matching a glob pattern under a base directory | |
| // | |
| static constexpr size_t SERVER_TOOL_FILE_SEARCH_MAX_RESULTS = 100; | |
| struct server_tool_file_glob_search : server_tool { | |
| server_tool_file_glob_search() { | |
| name = "file_glob_search"; | |
| display_name = "File search"; | |
| permission_write = false; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Recursively search for files matching a glob pattern under a directory."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"path", {{"type", "string"}, {"description", "Base directory to search in"}}}, | |
| {"include", {{"type", "string"}, {"description", "Glob pattern for files to include (e.g. \"**/*.cpp\"). Default: **"}}}, | |
| {"exclude", {{"type", "string"}, {"description", "Glob pattern for files to exclude"}}}, | |
| }}, | |
| {"required", json::array({"path"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string base = params.at("path").get<std::string>(); | |
| std::string include = json_value(params, "include", std::string("**")); | |
| std::string exclude = json_value(params, "exclude", std::string("")); | |
| std::ostringstream output_text; | |
| size_t count = 0; | |
| std::error_code ec; | |
| for (const auto & entry : fs::recursive_directory_iterator(base, | |
| fs::directory_options::skip_permission_denied, ec)) { | |
| if (!entry.is_regular_file()) continue; | |
| std::string rel = fs::relative(entry.path(), base, ec).string(); | |
| if (ec) continue; | |
| std::replace(rel.begin(), rel.end(), '\\', '/'); | |
| if (!glob_match(include, rel)) continue; | |
| if (!exclude.empty() && glob_match(exclude, rel)) continue; | |
| output_text << entry.path().string() << "\n"; | |
| if (++count >= SERVER_TOOL_FILE_SEARCH_MAX_RESULTS) { | |
| break; | |
| } | |
| } | |
| output_text << "\n---\nTotal matches: " << count << "\n"; | |
| return {{"plain_text_response", output_text.str()}}; | |
| } | |
| }; | |
| // | |
| // grep_search: search for a regex pattern in files | |
| // | |
| static constexpr size_t SERVER_TOOL_GREP_SEARCH_MAX_RESULTS = 100; | |
| struct server_tool_grep_search : server_tool { | |
| server_tool_grep_search() { | |
| name = "grep_search"; | |
| display_name = "Grep search"; | |
| permission_write = false; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Search for a regex pattern in files under a path. Returns matching lines."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"path", {{"type", "string"}, {"description", "File or directory to search in"}}}, | |
| {"pattern", {{"type", "string"}, {"description", "Regular expression pattern to search for"}}}, | |
| {"include", {{"type", "string"}, {"description", "Glob pattern to filter files (default: **)"}}}, | |
| {"exclude", {{"type", "string"}, {"description", "Glob pattern to exclude files"}}}, | |
| {"return_line_numbers", {{"type", "boolean"}, {"description", "If true, include line numbers in results"}}}, | |
| }}, | |
| {"required", json::array({"path", "pattern"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string path = params.at("path").get<std::string>(); | |
| std::string pat_str = params.at("pattern").get<std::string>(); | |
| std::string include = json_value(params, "include", std::string("**")); | |
| std::string exclude = json_value(params, "exclude", std::string("")); | |
| bool show_lineno = json_value(params, "return_line_numbers", false); | |
| std::regex pattern; | |
| try { | |
| pattern = std::regex(pat_str); | |
| } catch (const std::regex_error & e) { | |
| return {{"error", std::string("invalid regex: ") + e.what()}}; | |
| } | |
| std::ostringstream output_text; | |
| size_t total = 0; | |
| auto search_file = [&](const fs::path & fpath) { | |
| std::ifstream f(fpath); | |
| if (!f) return; | |
| std::string line; | |
| int lineno = 0; | |
| while (std::getline(f, line) && total < SERVER_TOOL_GREP_SEARCH_MAX_RESULTS) { | |
| lineno++; | |
| if (std::regex_search(line, pattern)) { | |
| output_text << fpath.string() << ":"; | |
| if (show_lineno) { | |
| output_text << lineno << ":"; | |
| } | |
| output_text << line << "\n"; | |
| total++; | |
| } | |
| } | |
| }; | |
| std::error_code ec; | |
| if (fs::is_regular_file(path, ec)) { | |
| search_file(path); | |
| } else if (fs::is_directory(path, ec)) { | |
| for (const auto & entry : fs::recursive_directory_iterator(path, | |
| fs::directory_options::skip_permission_denied, ec)) { | |
| if (!entry.is_regular_file()) continue; | |
| if (total >= SERVER_TOOL_GREP_SEARCH_MAX_RESULTS) break; | |
| std::string rel = fs::relative(entry.path(), path, ec).string(); | |
| if (ec) continue; | |
| std::replace(rel.begin(), rel.end(), '\\', '/'); | |
| if (!glob_match(include, rel)) continue; | |
| if (!exclude.empty() && glob_match(exclude, rel)) continue; | |
| search_file(entry.path()); | |
| } | |
| } else { | |
| return {{"error", "path does not exist: " + path}}; | |
| } | |
| output_text << "\n\n---\nTotal matches: " << total << "\n"; | |
| return {{"plain_text_response", output_text.str()}}; | |
| } | |
| }; | |
| // | |
| // exec_shell_command: run an arbitrary shell command | |
| // | |
| static constexpr size_t SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE = 16 * 1024; // 16 KB | |
| static constexpr int SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT = 60; // seconds | |
| struct server_tool_exec_shell_command : server_tool { | |
| server_tool_exec_shell_command() { | |
| name = "exec_shell_command"; | |
| display_name = "Execute shell command"; | |
| permission_write = true; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Execute a shell command and return its output (stdout and stderr combined)."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"command", {{"type", "string"}, {"description", "Shell command to execute"}}}, | |
| {"timeout", {{"type", "integer"}, {"description", string_format("Timeout in seconds (default 10, max %d)", SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT)}}}, | |
| {"max_output_size", {{"type", "integer"}, {"description", string_format("Maximum output size in bytes (default %zu)", SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE)}}}, | |
| }}, | |
| {"required", json::array({"command"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string command = params.at("command").get<std::string>(); | |
| int timeout = json_value(params, "timeout", 10); | |
| size_t max_output = (size_t) json_value(params, "max_output_size", (int) SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE); | |
| timeout = std::min(timeout, SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_TIMEOUT); | |
| max_output = std::min(max_output, SERVER_TOOL_EXEC_SHELL_COMMAND_MAX_OUTPUT_SIZE); | |
| std::vector<std::string> args = {"cmd", "/c", command}; | |
| std::vector<std::string> args = {"sh", "-c", command}; | |
| auto res = run_process(args, max_output, timeout); | |
| std::string text_output = res.output; | |
| text_output += string_format("\n[exit code: %d]", res.exit_code); | |
| if (res.timed_out) { | |
| text_output += " [exit due to timed out]"; | |
| } | |
| return {{"plain_text_response", text_output}}; | |
| } | |
| }; | |
| // | |
| // write_file: create or overwrite a file | |
| // | |
| struct server_tool_write_file : server_tool { | |
| server_tool_write_file() { | |
| name = "write_file"; | |
| display_name = "Write file"; | |
| permission_write = true; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Write content to a file, creating it (including parent directories) if it does not exist. May use with edit_file for more complex edits."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"path", {{"type", "string"}, {"description", "Path of the file to write"}}}, | |
| {"content", {{"type", "string"}, {"description", "Content to write"}}}, | |
| }}, | |
| {"required", json::array({"path", "content"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string path = params.at("path").get<std::string>(); | |
| std::string content = params.at("content").get<std::string>(); | |
| std::error_code ec; | |
| fs::path fpath(path); | |
| if (fpath.has_parent_path()) { | |
| fs::create_directories(fpath.parent_path(), ec); | |
| if (ec) { | |
| return {{"error", "failed to create directories: " + ec.message()}}; | |
| } | |
| } | |
| std::ofstream f(path, std::ios::binary); | |
| if (!f) { | |
| return {{"error", "failed to open file for writing: " + path}}; | |
| } | |
| f << content; | |
| if (!f) { | |
| return {{"error", "failed to write file: " + path}}; | |
| } | |
| return {{"result", "file written successfully"}, {"path", path}, {"bytes", content.size()}}; | |
| } | |
| }; | |
| // | |
| // edit_file: edit file content via line-based changes | |
| // | |
| struct server_tool_edit_file : server_tool { | |
| server_tool_edit_file() { | |
| name = "edit_file"; | |
| display_name = "Edit file"; | |
| permission_write = true; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", | |
| "Edit a file by applying a list of line-based changes. " | |
| "Each change targets a 1-based inclusive line range and has a mode: " | |
| "\"replace\" (replace lines with content), " | |
| "\"delete\" (remove lines, content must be empty string), " | |
| "\"append\" (insert content after line_end). " | |
| "Set line_start to -1 to target the end of file (line_end is ignored in that case). " | |
| "Changes must not overlap. They are applied in reverse line order automatically."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"path", {{"type", "string"}, {"description", "Path to the file to edit"}}}, | |
| {"changes", { | |
| {"type", "array"}, | |
| {"description", "List of changes to apply"}, | |
| {"items", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"mode", {{"type", "string"}, {"description", "\"replace\", \"delete\", or \"append\""}}}, | |
| {"line_start", {{"type", "integer"}, {"description", "First line of the range (1-based); use -1 for end of file"}}}, | |
| {"line_end", {{"type", "integer"}, {"description", "Last line of the range (1-based, inclusive); ignored when line_start is -1"}}}, | |
| {"content", {{"type", "string"}, {"description", "Content to insert; must be empty string for delete mode"}}}, | |
| }}, | |
| {"required", json::array({"mode", "line_start", "line_end", "content"})}, | |
| }}, | |
| }}, | |
| }}, | |
| {"required", json::array({"path", "changes"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string path = params.at("path").get<std::string>(); | |
| const json & changes = params.at("changes"); | |
| if (!changes.is_array()) { | |
| return {{"error", "\"changes\" must be an array"}}; | |
| } | |
| // read file into lines | |
| std::ifstream fin(path); | |
| if (!fin) { | |
| return {{"error", "failed to open file: " + path}}; | |
| } | |
| std::vector<std::string> lines; | |
| { | |
| std::string line; | |
| while (std::getline(fin, line)) { | |
| lines.push_back(line); | |
| } | |
| } | |
| fin.close(); | |
| // validate and collect changes, then sort descending by line_start | |
| struct change_entry { | |
| std::string mode; | |
| int line_start; // 1-based | |
| int line_end; // 1-based inclusive | |
| std::string content; | |
| }; | |
| std::vector<change_entry> entries; | |
| entries.reserve(changes.size()); | |
| for (const auto & ch : changes) { | |
| change_entry e; | |
| e.mode = ch.at("mode").get<std::string>(); | |
| e.line_start = ch.at("line_start").get<int>(); | |
| e.line_end = ch.at("line_end").get<int>(); | |
| e.content = ch.at("content").get<std::string>(); | |
| if (e.mode != "replace" && e.mode != "delete" && e.mode != "append") { | |
| return {{"error", "invalid mode \"" + e.mode + "\"; must be replace, delete, or append"}}; | |
| } | |
| if (e.mode == "delete" && !e.content.empty()) { | |
| return {{"error", "content must be empty string for delete mode"}}; | |
| } | |
| int n = (int) lines.size(); | |
| if (e.line_start == -1) { | |
| // -1 targets end of file -> valid for append only; line_end is ignored | |
| if (e.mode != "append") { | |
| return {{"error", "line_start -1 (end of file) is only valid for append mode"}}; | |
| } | |
| // append at end of file: insert position is the current line count | |
| e.line_start = n; | |
| e.line_end = n; | |
| } else { | |
| if (e.line_start < 1 || e.line_end < e.line_start) { | |
| return {{"error", string_format("invalid line range [%d, %d]", e.line_start, e.line_end)}}; | |
| } | |
| if (e.line_end > n) { | |
| return {{"error", string_format("line_end %d exceeds file length %d", e.line_end, n)}}; | |
| } | |
| } | |
| entries.push_back(std::move(e)); | |
| } | |
| // sort descending so earlier-indexed changes don't shift later ones | |
| std::sort(entries.begin(), entries.end(), [](const change_entry & a, const change_entry & b) { | |
| return a.line_start > b.line_start; | |
| }); | |
| // apply changes (0-based indices internally) | |
| for (const auto & e : entries) { | |
| int idx_start = e.line_start - 1; // 0-based | |
| int idx_end = e.line_end - 1; // 0-based inclusive | |
| // split content into lines (preserve trailing newline awareness) | |
| std::vector<std::string> new_lines; | |
| if (!e.content.empty()) { | |
| std::istringstream ss(e.content); | |
| std::string ln; | |
| while (std::getline(ss, ln)) { | |
| new_lines.push_back(ln); | |
| } | |
| // if content ends with \n, getline consumed it — no extra empty line needed | |
| // if content does NOT end with \n, last line is still captured correctly | |
| } | |
| if (e.mode == "replace") { | |
| // erase [idx_start, idx_end] and insert new_lines | |
| lines.erase(lines.begin() + idx_start, lines.begin() + idx_end + 1); | |
| lines.insert(lines.begin() + idx_start, new_lines.begin(), new_lines.end()); | |
| } else if (e.mode == "delete") { | |
| lines.erase(lines.begin() + idx_start, lines.begin() + idx_end + 1); | |
| } else { // append | |
| // insert after idx_end; idx_end + 1 == lines.size() for end-of-file append | |
| lines.insert(lines.begin() + (idx_end + 1), new_lines.begin(), new_lines.end()); | |
| } | |
| } | |
| // write file back | |
| std::ofstream fout(path, std::ios::binary); | |
| if (!fout) { | |
| return {{"error", "failed to open file for writing: " + path}}; | |
| } | |
| for (size_t i = 0; i < lines.size(); i++) { | |
| fout << lines[i]; | |
| if (i + 1 < lines.size()) { | |
| fout << "\n"; | |
| } | |
| } | |
| if (!lines.empty()) { | |
| fout << "\n"; | |
| } | |
| if (!fout) { | |
| return {{"error", "failed to write file: " + path}}; | |
| } | |
| return {{"result", "file edited successfully"}, {"path", path}, {"lines", (int) lines.size()}}; | |
| } | |
| }; | |
| // | |
| // apply_diff: apply a unified diff via git apply | |
| // | |
| struct server_tool_apply_diff : server_tool { | |
| server_tool_apply_diff() { | |
| name = "apply_diff"; | |
| display_name = "Apply diff"; | |
| permission_write = true; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Apply a unified diff to edit one or more files using git apply. Use this instead of edit_file when the changes are complex."}, | |
| {"parameters", { | |
| {"type", "object"}, | |
| {"properties", { | |
| {"diff", {{"type", "string"}, {"description", "Unified diff content in git diff format"}}}, | |
| }}, | |
| {"required", json::array({"diff"})}, | |
| }}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json params) override { | |
| std::string diff = params.at("diff").get<std::string>(); | |
| // write diff to a temporary file | |
| static std::atomic<int> counter{0}; | |
| std::string tmp_path = (fs::temp_directory_path() / | |
| ("llama_patch_" + std::to_string(++counter) + ".patch")).string(); | |
| { | |
| std::ofstream f(tmp_path, std::ios::binary); | |
| if (!f) { | |
| return {{"error", "failed to create temp patch file"}}; | |
| } | |
| f << diff; | |
| } | |
| auto res = run_process({"git", "apply", tmp_path}, 4096, 10); | |
| std::error_code ec; | |
| fs::remove(tmp_path, ec); | |
| if (res.exit_code != 0) { | |
| return {{"error", "git apply failed (exit " + std::to_string(res.exit_code) + "): " + res.output}}; | |
| } | |
| return {{"result", "patch applied successfully"}}; | |
| } | |
| }; | |
| // | |
| // get_datetime: returns the current date and time | |
| // | |
| struct server_tool_get_datetime : server_tool { | |
| server_tool_get_datetime() { | |
| name = "get_datetime"; | |
| display_name = "Get Date & Time"; | |
| permission_write = false; | |
| } | |
| json get_definition() override { | |
| return { | |
| {"type", "function"}, | |
| {"function", { | |
| {"name", name}, | |
| {"description", "Returns the current date and time"}, | |
| }}, | |
| }; | |
| } | |
| json invoke(json) override { | |
| auto now = std::chrono::system_clock::now(); | |
| auto time = std::chrono::system_clock::to_time_t(now); | |
| return {{"result", std::ctime(&time)}}; | |
| } | |
| }; | |
| // | |
| // public API | |
| // | |
| static std::vector<std::unique_ptr<server_tool>> build_tools() { | |
| std::vector<std::unique_ptr<server_tool>> tools; | |
| tools.push_back(std::make_unique<server_tool_read_file>()); | |
| tools.push_back(std::make_unique<server_tool_file_glob_search>()); | |
| tools.push_back(std::make_unique<server_tool_grep_search>()); | |
| tools.push_back(std::make_unique<server_tool_exec_shell_command>()); | |
| tools.push_back(std::make_unique<server_tool_write_file>()); | |
| tools.push_back(std::make_unique<server_tool_edit_file>()); | |
| tools.push_back(std::make_unique<server_tool_apply_diff>()); | |
| tools.push_back(std::make_unique<server_tool_get_datetime>()); | |
| return tools; | |
| } | |
| void server_tools::setup(const std::vector<std::string> & enabled_tools) { | |
| if (!enabled_tools.empty()) { | |
| std::unordered_set<std::string> enabled_set(enabled_tools.begin(), enabled_tools.end()); | |
| auto all_tools = build_tools(); | |
| // collect all known tool names for validation | |
| std::vector<std::string> known_names; | |
| known_names.reserve(all_tools.size()); | |
| for (const auto & t : all_tools) { | |
| known_names.push_back(t->name); | |
| } | |
| // validate that every requested tool is known | |
| for (const auto & name : enabled_tools) { | |
| if (name == "all") continue; | |
| if (std::find(known_names.begin(), known_names.end(), name) == known_names.end()) { | |
| throw std::runtime_error(string_format( | |
| "unknown tool \"%s\". available tools: %s", | |
| name.c_str(), | |
| string_join(known_names, ", ").c_str())); | |
| } | |
| } | |
| tools.clear(); | |
| for (auto & t : all_tools) { | |
| if (enabled_set.count(t->name) > 0 || enabled_set.count("all") > 0) { | |
| tools.push_back(std::move(t)); | |
| } | |
| } | |
| } | |
| handle_get = [this](const server_http_req &) -> server_http_res_ptr { | |
| auto res = std::make_unique<server_http_res>(); | |
| try { | |
| json result = json::array(); | |
| for (const auto & t : tools) { | |
| result.push_back(t->to_json()); | |
| } | |
| res->data = safe_json_to_str(result); | |
| } catch (const std::exception & e) { | |
| SRV_ERR("got exception: %s\n", e.what()); | |
| res->status = 500; | |
| res->data = safe_json_to_str(format_error_response(e.what(), ERROR_TYPE_SERVER)); | |
| } | |
| return res; | |
| }; | |
| handle_post = [this](const server_http_req & req) -> server_http_res_ptr { | |
| auto res = std::make_unique<server_http_res>(); | |
| try { | |
| json body = json::parse(req.body); | |
| std::string tool_name = body.at("tool").get<std::string>(); | |
| json params = body.value("params", json::object()); | |
| json result = invoke(tool_name, params); | |
| res->data = safe_json_to_str(result); | |
| } catch (const json::exception & e) { | |
| res->status = 400; | |
| res->data = safe_json_to_str(format_error_response(e.what(), ERROR_TYPE_INVALID_REQUEST)); | |
| } catch (const std::exception & e) { | |
| SRV_ERR("got exception: %s\n", e.what()); | |
| res->status = 500; | |
| res->data = safe_json_to_str(format_error_response(e.what(), ERROR_TYPE_SERVER)); | |
| } | |
| return res; | |
| }; | |
| } | |
| json server_tools::invoke(const std::string & name, const json & params) { | |
| for (auto & t : tools) { | |
| if (t->name == name) { | |
| return t->invoke(params); | |
| } | |
| } | |
| return {{"error", "unknown tool: " + name}}; | |
| } | |