Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -612,10 +612,13 @@ dfx canister call llama_cpp recursive_dir_content_update '(record {dir = ".canis
dfx canister call llama_cpp recursive_dir_content_update '(record {dir = ".canister_cache"; max_entries = 5000 : nat64})' --output json

# Get the size of a file in bytes
dfx canister --network $NETWORK call $llm filesystem_file_size '(record {filename = "<filename>"})'
dfx canister call llama_cpp filesystem_file_size '(record {filename = "<filename>"})' --output json

# Get the creation timestamp of a file in nanoseconds (also returns age of file in seconds)
dfx canister call llama_cpp get_creation_timestamp_ns '(record {filename = "<filename>"})' --output json

# remove a file or empty directory
dfx canister --network $NETWORK call $llm filesystem_remove '(record {filename = "<filename>"})'
dfx canister call llama_cpp filesystem_remove '(record {filename = "<filename>"})'
```

# Appendix A: max_tokens
Expand Down
2 changes: 1 addition & 1 deletion native/test_files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -230,7 +230,7 @@ void test_files(MockIC &mockIC) {
std::string(__func__) + ": " + "filesystem_file_size - non-existing file",
filesystem_file_size,
"4449444c016c01c7dda8bb0771010012646f65735f6e6f745f65786973742e62696e",
"4449444c026c04c1b4cc0271c7dda8bb0771bdbaf9d50778dcbb80ff0b7e6b01bc8a01000101002846696c6520646f6573206e6f742065786973743a20646f65735f6e6f745f65786973742e62696e0a12646f65735f6e6f745f65786973742e62696e000000000000000000",
"4449444c026b01b0ad8fcd0c716b01c5fed20100010100002746696c6520646f6573206e6f742065786973743a20646f65735f6e6f745f65786973742e62696e",
silent_on_trap, my_principal);

// -----------------------------------------------------------------------------
Expand Down
1 change: 1 addition & 0 deletions src/db_chats.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "db_chats.h"
#include "auth.h"
#include "common.h"
#include "files.h"
#include "http.h"
#include "main_.h"
#include "max_tokens.h"
Expand Down
116 changes: 107 additions & 9 deletions src/files.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
// This library is included with icpp-pro
#include "hash-library/sha256.h"

#include <chrono>
#include <cstdint>
#include <fstream>
#include <iostream>
Expand All @@ -31,6 +32,20 @@ void filesystem_file_size() {
filesystem_file_size_(ic_api, filename, true);
}

void get_creation_timestamp_ns() {
IC_API ic_api(CanisterQuery{std::string(__func__)}, false);
if (!is_caller_a_controller(ic_api)) return;

// Get filename
std::string filename{""};

CandidTypeRecord r_in;
r_in.append("filename", CandidTypeText{&filename});
ic_api.from_wire(r_in);

get_creation_timestamp_ns_(ic_api, filename, true);
}

void filesystem_remove() {
IC_API ic_api(CanisterUpdate{std::string(__func__)}, false);
if (!is_caller_a_controller(ic_api)) return;
Expand Down Expand Up @@ -175,7 +190,8 @@ std::uint64_t filesystem_file_size_(IC_API &ic_api, const std::string &filename,
error = true;
msg = "Error: " + ec.message() + "\n";
} else if (!exists) {
msg = "File does not exist: " + filename + "\n";
error = true;
msg = "File does not exist: " + filename;
} else {
msg = "File exists: " + filename + "\n";
// Use the non-throwing version of std::filesystem::file_size
Expand All @@ -193,18 +209,93 @@ std::uint64_t filesystem_file_size_(IC_API &ic_api, const std::string &filename,
<< std::endl;

if (to_wire) {
// Return the filesize over the wire (caller must immediately return from endpoint)
CandidTypeRecord filesystem_file_size_record;
filesystem_file_size_record.append("exists", CandidTypeBool{exists});
filesystem_file_size_record.append("filename", CandidTypeText{filename});
filesystem_file_size_record.append("filesize", CandidTypeNat64{filesize});
filesystem_file_size_record.append("msg", CandidTypeText{msg});
ic_api.to_wire(
CandidTypeVariant{"Ok", CandidTypeRecord{filesystem_file_size_record}});
if (error) {
ic_api.to_wire(CandidTypeVariant{
"Err", CandidTypeVariant{"Other", CandidTypeText{msg}}});
} else {
// Return the filesize over the wire (caller must immediately return from endpoint)
CandidTypeRecord filesystem_file_size_record;
filesystem_file_size_record.append("exists", CandidTypeBool{exists});
filesystem_file_size_record.append("filename", CandidTypeText{filename});
filesystem_file_size_record.append("filesize", CandidTypeNat64{filesize});
filesystem_file_size_record.append("msg", CandidTypeText{msg});
ic_api.to_wire(CandidTypeVariant{
"Ok", CandidTypeRecord{filesystem_file_size_record}});
}
}
return filesize;
}

std::uint64_t get_creation_timestamp_ns_(IC_API &ic_api,
const std::string &filename,
bool to_wire) {
std::error_code ec;
std::string msg;
bool error = false;
std::uint64_t timestamp_ns = 0;
std::uint64_t age_seconds = 0;

bool exists = std::filesystem::exists(filename, ec);
if (ec) {
error = true;
msg = "Error: " + ec.message() + "\n";
} else if (!exists) {
error = true;
msg = "File does not exist: " + filename;
} else {
msg = "File exists: " + filename + "\n";
// Use the non-throwing version of std::filesystem::last_write_time
// NOTE: on the IC, this is actually the creation time, not the last write time
auto ftime = std::filesystem::last_write_time(filename, ec);
if (ec) {
error = true;
msg += "Error: " + ec.message() + "\n";
} else {
// Get time since epoch in nanoseconds
auto nanos_since_epoch =
std::chrono::duration_cast<std::chrono::nanoseconds>(
ftime.time_since_epoch())
.count();
timestamp_ns = static_cast<std::uint64_t>(nanos_since_epoch);

// Calculate age in seconds
auto now = std::chrono::file_clock::now();
auto age = std::chrono::duration_cast<std::chrono::seconds>(
now.time_since_epoch() - ftime.time_since_epoch())
.count();
age_seconds = static_cast<std::uint64_t>(age);

msg +=
"File creation timestamp_ns: " + std::to_string(nanos_since_epoch) +
" ns" + "\n" + "File creation age: " + std::to_string(age_seconds) +
" seconds" + "\n";
}
}

std::cout << "llama_cpp: " << std::string(__func__) << " - " << msg
<< std::endl;

if (to_wire) {
if (error) {
ic_api.to_wire(CandidTypeVariant{
"Err", CandidTypeVariant{"Other", CandidTypeText{msg}}});
} else {
// Return the timestamp & age over the wire (caller must immediately return from endpoint)
CandidTypeRecord filesystem_timestamp_record;
filesystem_timestamp_record.append("exists", CandidTypeBool{exists});
filesystem_timestamp_record.append("filename", CandidTypeText{filename});
filesystem_timestamp_record.append("timestamp_ns",
CandidTypeNat64{timestamp_ns});
filesystem_timestamp_record.append("age_seconds",
CandidTypeNat64{age_seconds});
filesystem_timestamp_record.append("msg", CandidTypeText{msg});
ic_api.to_wire(CandidTypeVariant{
"Ok", CandidTypeRecord{filesystem_timestamp_record}});
}
}
return timestamp_ns;
}

std::vector<FileEntry>
list_directory_contents(const std::filesystem::path &dir,
const std::uint64_t &max_entries) {
Expand Down Expand Up @@ -252,3 +343,10 @@ list_directory_contents(const std::filesystem::path &dir,

return entries;
}

// Helper function to retrieve the last write time of a file
// NOTE: on the IC, this is actually the creation time, not the last write time
std::filesystem::file_time_type
get_last_write_time(const std::filesystem::path &file, std::error_code &ec) {
return std::filesystem::last_write_time(file, ec);
}
12 changes: 11 additions & 1 deletion src/files.h
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ void filesystem_remove()
WASM_SYMBOL_EXPORTED("canister_update filesystem_remove");
void filesystem_file_size()
WASM_SYMBOL_EXPORTED("canister_query filesystem_file_size");
void get_creation_timestamp_ns()
WASM_SYMBOL_EXPORTED("canister_query get_creation_timestamp_ns");
void recursive_dir_content_query()
WASM_SYMBOL_EXPORTED("canister_query recursive_dir_content_query");
void recursive_dir_content_update()
Expand All @@ -22,11 +24,19 @@ bool filesystem_remove_(IC_API &ic_api, const std::string &filename, bool all,
bool to_wire = true);
std::uint64_t filesystem_file_size_(IC_API &ic_api, const std::string &filename,
bool to_wire = true);
std::uint64_t get_creation_timestamp_ns_(IC_API &ic_api,
const std::string &filename,
bool to_wire = true);

struct FileEntry {
std::string filename;
std::string filetype; // "file" or "directory"
};
std::vector<FileEntry>
list_directory_contents(const std::filesystem::path &dir,
const std::uint64_t &max_entries);
const std::uint64_t &max_entries);

// Helper function to retrieve the last write time of a file
// NOTE: on the IC, this is actually the creation time, not the last write time
std::filesystem::file_time_type
get_last_write_time(const std::filesystem::path &file, std::error_code &ec);
19 changes: 18 additions & 1 deletion src/llama_cpp.did
Original file line number Diff line number Diff line change
Expand Up @@ -94,7 +94,7 @@ type GetChatsRecordResult = variant {
};
type GetChatsRecord = record {
chats : vec record {
timestamp : text;
timestamp_ns : text;
chat : text
}
};
Expand Down Expand Up @@ -156,6 +156,22 @@ type FilesystemFileSizeRecord = record {
msg : text
};

// -----------------------------------------------------
type FilesystemTimestampInputRecord = record {
filename : text
};
type FilesystemTimestampRecordResult = variant {
Err : ApiError;
Ok : FilesystemTimestampRecord
};
type FilesystemTimestampRecord = record {
exists : bool;
filename : text;
timestamp_ns : nat64; // the file creation timestamp in nanoseconds
age_seconds : nat64; // the file creation age in seconds
msg : text
};

// -----------------------------------------------------
type FilesystemRemoveInputRecord = record {
filename : text
Expand Down Expand Up @@ -218,6 +234,7 @@ service : {
// Files - general utility endpoints exposing std::filesystem functions
filesystem_remove : (FilesystemRemoveInputRecord) -> (FilesystemRemoveRecordResult);
filesystem_file_size : (FilesystemFileSizeInputRecord) -> (FilesystemFileSizeRecordResult) query;
get_creation_timestamp_ns : (FilesystemTimestampInputRecord) -> (FilesystemTimestampRecordResult) query;
recursive_dir_content_query : (DirContentInputRecord) -> (DirContentRecordResult) query;
recursive_dir_content_update : (DirContentInputRecord) -> (DirContentRecordResult);

Expand Down
6 changes: 0 additions & 6 deletions src/utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -86,10 +86,4 @@ void send_output_record_result_error_to_wire(IC_API &ic_api,
r_out.append("prompt_remaining", CandidTypeText{""});
r_out.append("generated_eog", CandidTypeBool{false});
ic_api.to_wire(CandidTypeVariant{"Err", r_out});
}

// Helper function to retrieve the last write time of a file
std::filesystem::file_time_type
get_last_write_time(const std::filesystem::path &file, std::error_code &ec) {
return std::filesystem::last_write_time(file, ec);
}
5 changes: 1 addition & 4 deletions src/utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,4 @@ bool my_create_directory(const std::filesystem::path &dir_path,

void send_output_record_result_error_to_wire(IC_API &ic_api,
uint16_t http_status_code,
const std::string &error_msg);

std::filesystem::file_time_type
get_last_write_time(const std::filesystem::path &file, std::error_code &ec);
const std::string &error_msg);
61 changes: 60 additions & 1 deletion test/test_files.py
Original file line number Diff line number Diff line change
Expand Up @@ -151,7 +151,7 @@ def test__filesystem_file_size_non_existing(network: str, principal: str) -> Non
canister_argument='(record {filename = "does_not_exist.bin"})',
network=network,
)
expected_response = f'(variant {{ Ok = record {{ msg = "File does not exist: does_not_exist.bin\\n"; filename = "does_not_exist.bin"; filesize = 0 : nat64; exists = false;}} }})'
expected_response = '(variant { Err = variant { Other = "File does not exist: does_not_exist.bin" } })'
assert response == expected_response

def test__filesystem_file_size_anonymous(identity_anonymous: Dict[str, str], network: str) -> None:
Expand Down Expand Up @@ -201,6 +201,65 @@ def test__filesystem_file_size_controller(network: str, principal: str) -> None:
expected_response = f'(variant {{ Ok = record {{ msg = "File exists: .canister_cache/{principal}/sessions/prompt.cache\\nFile size: 5 bytes\\n"; filename = ".canister_cache/{principal}/sessions/prompt.cache"; filesize = 5 : nat64; exists = true;}} }})'
assert response == expected_response

# ------------------------------------------------------------------
def test__get_creation_timestamp_ns_non_existing(network: str, principal: str) -> None:
response = call_canister_api(
dfx_json_path=DFX_JSON_PATH,
canister_name=CANISTER_NAME,
canister_method="get_creation_timestamp_ns",
canister_argument='(record {filename = "does_not_exist.bin"})',
network=network,
)
expected_response = '(variant { Err = variant { Other = "File does not exist: does_not_exist.bin" } })'
assert response == expected_response

def test__get_creation_timestamp_ns_anonymous(identity_anonymous: Dict[str, str], network: str) -> None:
# double check the identity_anonymous fixture worked
assert identity_anonymous["identity"] == "anonymous"
assert identity_anonymous["principal"] == "2vxsx-fae"

principal = identity_anonymous["principal"]
filename = f".canister_cache/{principal}/sessions/prompt.cache"

response = call_canister_api(
dfx_json_path=DFX_JSON_PATH,
canister_name=CANISTER_NAME,
canister_method="get_creation_timestamp_ns",
canister_argument=f'(record {{filename = "{filename}"}})',
network=network,
)
expected_response = f'(variant {{ Err = variant {{ Other = "Access Denied" }} }})'
assert response == expected_response

# This test requires to run the test with non-default identity --> TODO: qa script must run with non-default identity
#
# def test__get_creation_timestamp_ns_non_controller(identity_default: Dict[str, str], network: str) -> None:
# principal = identity_default["principal"]
# filename = f".canister_cache/{principal}/sessions/prompt.cache"

# response = call_canister_api(
# dfx_json_path=DFX_JSON_PATH,
# canister_name=CANISTER_NAME,
# canister_method="get_creation_timestamp_ns",
# canister_argument=f'(record {{filename = "{filename}"}})',
# network=network,
# )
# expected_response = f'(variant {{ Err = variant {{ Other = "Access Denied" }} }})'
# assert response == expected_response

def test__get_creation_timestamp_ns_controller(network: str, principal: str) -> None:
filename = f".canister_cache/{principal}/sessions/prompt.cache"

response = call_canister_api(
dfx_json_path=DFX_JSON_PATH,
canister_name=CANISTER_NAME,
canister_method="get_creation_timestamp_ns",
canister_argument=f'(record {{filename = "{filename}"}})',
network=network,
)
expected_response_startswith = f'(variant {{ Ok = record {{ msg = "File exists: .canister_cache/{principal}/sessions/prompt.cache\\nFile creation timestamp_ns: '
assert response.startswith(expected_response_startswith)

# ------------------------------------------------------------------
def test__filesystem_remove_non_existing(network: str, principal: str) -> None:
response = call_canister_api(
Expand Down
Loading