diff --git a/README.md b/README.md index 8a74053..e8baa1c 100644 --- a/README.md +++ b/README.md @@ -55,11 +55,11 @@ Skills compose by adoption-list order (`10123`) and trigger tags carry runtime e Didactyl will support local inference, which is very privacy preserving. Remote inference does however have it's advantages, and in those cases Didactyl supports using Bitcoin Lightning and eCash inference providers. -## Current Status — v0.0.84 +## Current Status — v0.0.85 **Active build — this project is barely working. Experiment at your own risk.** -> Last release update: v0.0.84 — Remove duplicated read-only tool payload fields and make slash output prefer content +> Last release update: v0.0.85 — Skip llm_config republish on startup when config was recalled from Nostr - Connects to configured relays with auto-reconnect and relay state transition logging - Publishes configured startup events per relay as each relay becomes connected diff --git a/src/main.c b/src/main.c index bf34343..77059b6 100644 --- a/src/main.c +++ b/src/main.c @@ -813,24 +813,29 @@ static int publish_encrypted_self_config(const didactyl_config_t* cfg, const cha return rc; } -static int persist_runtime_config_to_nostr(const didactyl_config_t* cfg) { +static int persist_runtime_config_to_nostr(const didactyl_config_t* cfg, int persist_llm_config) { if (!cfg) return -1; - cJSON* llm = cJSON_CreateObject(); - if (!llm) return -1; - cJSON_AddStringToObject(llm, "provider", cfg->llm.provider); - cJSON_AddStringToObject(llm, "api_key", cfg->llm.api_key); - cJSON_AddStringToObject(llm, "model", cfg->llm.model); - cJSON_AddStringToObject(llm, "base_url", cfg->llm.base_url); - cJSON_AddNumberToObject(llm, "max_tokens", cfg->llm.max_tokens); - cJSON_AddNumberToObject(llm, "temperature", cfg->llm.temperature); + int llm_rc = 0; + if (persist_llm_config) { + cJSON* llm = cJSON_CreateObject(); + if (!llm) return -1; + cJSON_AddStringToObject(llm, "provider", cfg->llm.provider); + cJSON_AddStringToObject(llm, "api_key", cfg->llm.api_key); + cJSON_AddStringToObject(llm, "model", cfg->llm.model); + cJSON_AddStringToObject(llm, "base_url", cfg->llm.base_url); + cJSON_AddNumberToObject(llm, "max_tokens", cfg->llm.max_tokens); + cJSON_AddNumberToObject(llm, "temperature", cfg->llm.temperature); - char* llm_json = cJSON_PrintUnformatted(llm); - cJSON_Delete(llm); - if (!llm_json) return -1; + char* llm_json = cJSON_PrintUnformatted(llm); + cJSON_Delete(llm); + if (!llm_json) return -1; - int llm_rc = publish_encrypted_self_config(cfg, "llm_config", llm_json); - free(llm_json); + llm_rc = publish_encrypted_self_config(cfg, "llm_config", llm_json); + free(llm_json); + } else { + DEBUG_INFO("[didactyl] startup phase: skipping llm_config publish because it was recalled from Nostr"); + } cJSON* agent = cJSON_CreateObject(); if (!agent) return llm_rc; @@ -846,15 +851,18 @@ static int persist_runtime_config_to_nostr(const didactyl_config_t* cfg) { return (llm_rc == 0 && agent_rc == 0) ? 0 : -1; } -static void recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg) { +static int recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg) { if (!cfg) { - return; + return 0; } + int llm_recalled_from_nostr = 0; + if (!llm_config_is_complete(&cfg->llm)) { char* llm_plaintext = NULL; if (fetch_self_config_plaintext(cfg, "llm_config", &llm_plaintext) == 0 && llm_plaintext) { if (apply_recalled_llm_config(cfg, llm_plaintext) == 0) { + llm_recalled_from_nostr = 1; DEBUG_INFO("[didactyl] startup phase: recovered llm_config from Nostr"); } else { DEBUG_WARN("[didactyl] startup phase: failed to apply recalled llm_config"); @@ -885,6 +893,8 @@ static void recover_missing_runtime_config_from_nostr(didactyl_config_t* cfg) { DEBUG_WARN("[didactyl] startup phase: agent_config recall unavailable"); } free(agent_plaintext); + + return llm_recalled_from_nostr; } int main(int argc, char** argv) { @@ -1129,8 +1139,8 @@ int main(int argc, char** argv) { } startup_step_begin(2, "Recover runtime config from Nostr"); - recover_missing_runtime_config_from_nostr(&cfg); - startup_step_ok(2, "Recover runtime config from Nostr", NULL); + int llm_recalled_from_nostr = recover_missing_runtime_config_from_nostr(&cfg); + startup_step_ok(2, "Recover runtime config from Nostr", llm_recalled_from_nostr ? "llm_config recalled" : NULL); startup_step_begin(3, "Validate LLM config"); if (!llm_config_is_complete(&cfg.llm)) { @@ -1184,7 +1194,7 @@ int main(int argc, char** argv) { } } - if (persist_runtime_config_to_nostr(&cfg) != 0) { + if (persist_runtime_config_to_nostr(&cfg, llm_recalled_from_nostr ? 0 : 1) != 0) { DEBUG_WARN("[didactyl] startup phase: failed to persist encrypted runtime config to Nostr"); } else { DEBUG_INFO("[didactyl] startup phase: persisted encrypted runtime config to Nostr"); diff --git a/src/main.h b/src/main.h index bb542fe..3babd58 100644 --- a/src/main.h +++ b/src/main.h @@ -12,8 +12,8 @@ // Using DIDACTYL_ prefix to avoid conflicts with nostr_core_lib VERSION macros #define DIDACTYL_VERSION_MAJOR 0 #define DIDACTYL_VERSION_MINOR 0 -#define DIDACTYL_VERSION_PATCH 84 -#define DIDACTYL_VERSION "v0.0.84" +#define DIDACTYL_VERSION_PATCH 85 +#define DIDACTYL_VERSION "v0.0.85" // Agent metadata #define DIDACTYL_NAME "Didactyl" diff --git a/src/tools/tool_model.c b/src/tools/tool_model.c index 1da38b4..c5aeaca 100644 --- a/src/tools/tool_model.c +++ b/src/tools/tool_model.c @@ -108,6 +108,25 @@ static int assign_string_field(cJSON* item, char* dst, size_t dst_size, int* cha return 0; } +static void mask_api_key_local(const char* api_key, char* out, size_t out_size) { + if (!out || out_size == 0) return; + out[0] = '\0'; + if (!api_key || api_key[0] == '\0') { + return; + } + + size_t api_len = strlen(api_key); + if (api_len >= 8U) { + snprintf(out, + out_size, + "%.4s...%s", + api_key, + api_key + api_len - 4U); + } else { + snprintf(out, out_size, "%s", "(set)"); + } +} + char* execute_model_get(const char* args_json) { cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); @@ -120,10 +139,15 @@ char* execute_model_get(const char* args_json) { cJSON* out = cJSON_CreateObject(); if (!out) return NULL; + + char api_key_masked[64] = {0}; + mask_api_key_local(cfg.api_key, api_key_masked, sizeof(api_key_masked)); + cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "provider", cfg.provider); cJSON_AddStringToObject(out, "model", cfg.model); cJSON_AddStringToObject(out, "base_url", cfg.base_url); + cJSON_AddStringToObject(out, "api_key_masked", api_key_masked); cJSON_AddNumberToObject(out, "max_tokens", cfg.max_tokens); cJSON_AddNumberToObject(out, "temperature", cfg.temperature);