#define _POSIX_C_SOURCE 200809L #include "tools_internal.h" #include #include #include #include "cjson/cJSON.h" #include "../main.h" #include "../nostr_handler.h" #include "../../nostr_core_lib/nostr_core/nostr_core.h" static char* json_error_local(const char* msg) { cJSON* root = cJSON_CreateObject(); if (!root) return NULL; cJSON_AddBoolToObject(root, "success", 0); cJSON_AddStringToObject(root, "error", msg ? msg : "unknown error"); char* out = cJSON_PrintUnformatted(root); cJSON_Delete(root); return out; } static cJSON* parse_args_local(const char* args_json) { const char* raw = args_json ? args_json : "{}"; cJSON* args = cJSON_Parse(raw); if (!args || !cJSON_IsObject(args)) { cJSON_Delete(args); return NULL; } return args; } static int append_text_local(char** buf, size_t* cap, size_t* used, const char* s) { if (!buf || !cap || !used || !s) return -1; size_t n = strlen(s); if (*used + n + 1U > *cap) { size_t next = *cap; while (*used + n + 1U > next) { next = (next == 0U) ? 256U : (next * 2U); } char* grown = (char*)realloc(*buf, next); if (!grown) return -1; *buf = grown; *cap = next; } memcpy(*buf + *used, s, n); *used += n; (*buf)[*used] = '\0'; return 0; } char* execute_message_current(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); cJSON* out = cJSON_CreateObject(); if (!out) return NULL; cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", ctx->template_current_user_message ? ctx->template_current_user_message : ""); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; } char* execute_agent_identity(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); char npub[128] = {0}; if (nostr_key_to_bech32(ctx->cfg->keys.public_key, "npub", npub) != NOSTR_SUCCESS) { strcpy(npub, "unknown"); } cJSON* out = cJSON_CreateObject(); if (!out) return NULL; cJSON_AddBoolToObject(out, "success", 1); cJSON* identity = cJSON_CreateObject(); if (!identity) { cJSON_Delete(out); return NULL; } cJSON_AddStringToObject(identity, "pubkey", ctx->cfg->keys.public_key_hex[0] ? ctx->cfg->keys.public_key_hex : "unknown"); cJSON_AddStringToObject(identity, "npub", npub); char* identity_json = cJSON_PrintUnformatted(identity); cJSON_Delete(identity); if (!identity_json) { cJSON_Delete(out); return NULL; } cJSON_AddStringToObject(out, "content", identity_json); free(identity_json); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; } char* execute_nostr_agent_profile(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); char* kind0 = nostr_handler_get_agent_kind0_context(); const char* profile_json = (kind0 && kind0[0]) ? kind0 : "{}"; cJSON* out = cJSON_CreateObject(); if (!out) { free(kind0); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", profile_json); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(kind0); return json; } char* execute_nostr_agent_contacts(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); char* kind3 = nostr_handler_get_agent_kind3_context(); const char* contacts_json = (kind3 && kind3[0]) ? kind3 : "[]"; cJSON* out = cJSON_CreateObject(); if (!out) { free(kind3); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", contacts_json); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(kind3); return json; } char* execute_nostr_agent_relays(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); char* kind10002 = nostr_handler_get_agent_kind10002_context(); const char* relays_json = (kind10002 && kind10002[0]) ? kind10002 : "[]"; cJSON* out = cJSON_CreateObject(); if (!out) { free(kind10002); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", relays_json); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(kind10002); return json; } char* execute_nostr_agent_notes(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); char* notes = nostr_handler_get_agent_kind1_notes_context(); const char* notes_text = (notes && notes[0]) ? notes : ""; cJSON* out = cJSON_CreateObject(); if (!out) { free(notes); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", notes_text); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(notes); return json; } char* execute_adopted_skills(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); cJSON* filter = cJSON_CreateObject(); cJSON* kinds = cJSON_CreateArray(); cJSON* authors = cJSON_CreateArray(); if (!filter || !kinds || !authors) { cJSON_Delete(filter); cJSON_Delete(kinds); cJSON_Delete(authors); return NULL; } cJSON_AddItemToArray(kinds, cJSON_CreateNumber(10123)); cJSON_AddItemToObject(filter, "kinds", kinds); cJSON_AddItemToArray(authors, cJSON_CreateString(ctx->cfg->keys.public_key_hex)); cJSON_AddItemToObject(filter, "authors", authors); cJSON_AddNumberToObject(filter, "limit", 1); char* events_json = nostr_handler_query_json(filter, 3000); cJSON_Delete(filter); cJSON* out = cJSON_CreateObject(); if (!out) { free(events_json); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", (events_json && events_json[0]) ? events_json : "[]"); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); free(events_json); return json; } char* execute_trigger_event(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); const char* trigger_json = (ctx->template_trigger_event_json && ctx->template_trigger_event_json[0] != '\0') ? ctx->template_trigger_event_json : NULL; if (!trigger_json) { cJSON* arg_json = cJSON_GetObjectItemCaseSensitive(args, "trigger_event_json"); if (arg_json && cJSON_IsString(arg_json) && arg_json->valuestring && arg_json->valuestring[0] != '\0') { trigger_json = arg_json->valuestring; } } cJSON_Delete(args); if (!trigger_json) { trigger_json = "{}"; } cJSON* out = cJSON_CreateObject(); if (!out) return NULL; cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", trigger_json); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; } char* execute_nostr_dm_history(tools_context_t* ctx, const char* args_json) { if (!ctx || !ctx->cfg) return json_error_local("tool context unavailable"); cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); int limit = 12; cJSON* limit_j = cJSON_GetObjectItemCaseSensitive(args, "limit"); if (limit_j && cJSON_IsNumber(limit_j)) { limit = (int)limit_j->valuedouble; } if (limit < 1) limit = 1; if (limit > 200) limit = 200; int include_current = 0; cJSON* include_current_j = cJSON_GetObjectItemCaseSensitive(args, "include_current"); if (include_current_j && cJSON_IsBool(include_current_j) && cJSON_IsTrue(include_current_j)) { include_current = 1; } int format_text = 0; cJSON* format_j = cJSON_GetObjectItemCaseSensitive(args, "format"); if (format_j && cJSON_IsString(format_j) && format_j->valuestring) { if (strcmp(format_j->valuestring, "text") == 0) { format_text = 1; } } const char* peer_pubkey = ctx->cfg->admin.pubkey; cJSON* peer_j = cJSON_GetObjectItemCaseSensitive(args, "peer_pubkey"); if (peer_j && cJSON_IsString(peer_j) && peer_j->valuestring && peer_j->valuestring[0] != '\0') { peer_pubkey = peer_j->valuestring; } char* history_json = nostr_handler_get_dm_history_json(peer_pubkey, limit); cJSON_Delete(args); if (!history_json) { return json_error_local("failed to fetch dm history"); } cJSON* parsed = cJSON_Parse(history_json); free(history_json); if (!parsed || !cJSON_IsArray(parsed)) { cJSON_Delete(parsed); parsed = cJSON_CreateArray(); if (!parsed) { return json_error_local("failed to build dm history response"); } } cJSON* filtered = cJSON_CreateArray(); if (!filtered) { cJSON_Delete(parsed); return json_error_local("failed to build dm history response"); } const char* current_message = (!include_current && ctx->template_current_user_message) ? ctx->template_current_user_message : NULL; const char* last_role = NULL; const char* last_content = NULL; int n = cJSON_GetArraySize(parsed); for (int i = 0; i < n; i++) { cJSON* item = cJSON_GetArrayItem(parsed, i); cJSON* role = item ? cJSON_GetObjectItemCaseSensitive(item, "role") : NULL; cJSON* content = item ? cJSON_GetObjectItemCaseSensitive(item, "content") : NULL; cJSON* created_at = item ? cJSON_GetObjectItemCaseSensitive(item, "created_at") : NULL; if (!role || !content || !cJSON_IsString(role) || !cJSON_IsString(content) || !role->valuestring || !content->valuestring) { continue; } const char* role_s = role->valuestring; const char* content_s = content->valuestring; int role_is_user = (strcmp(role_s, "user") == 0); if (!role_is_user && strcmp(role_s, "assistant") != 0) { continue; } if (i == n - 1 && role_is_user && current_message && strcmp(content_s, current_message) == 0) { continue; } if (last_role && last_content && strcmp(last_role, role_s) == 0 && strcmp(last_content, content_s) == 0) { continue; } cJSON* out_item = cJSON_CreateObject(); if (!out_item) { continue; } cJSON_AddStringToObject(out_item, "role", role_s); cJSON_AddStringToObject(out_item, "content", content_s); if (created_at && cJSON_IsNumber(created_at)) { cJSON_AddNumberToObject(out_item, "created_at", created_at->valuedouble); } cJSON_AddItemToArray(filtered, out_item); last_role = role_s; last_content = content_s; } cJSON_Delete(parsed); char* rendered = NULL; if (format_text) { size_t cap = 256U; size_t used = 0U; rendered = (char*)malloc(cap); if (!rendered) { cJSON_Delete(filtered); return json_error_local("failed to serialize dm history"); } rendered[0] = '\0'; int m = cJSON_GetArraySize(filtered); for (int i = 0; i < m; i++) { cJSON* item = cJSON_GetArrayItem(filtered, i); cJSON* role = item ? cJSON_GetObjectItemCaseSensitive(item, "role") : NULL; cJSON* content = item ? cJSON_GetObjectItemCaseSensitive(item, "content") : NULL; if (!role || !content || !cJSON_IsString(role) || !cJSON_IsString(content) || !role->valuestring || !content->valuestring) { continue; } const char* prefix = (strcmp(role->valuestring, "user") == 0) ? "- **Admin**: " : "- **You**: "; if (append_text_local(&rendered, &cap, &used, prefix) != 0 || append_text_local(&rendered, &cap, &used, content->valuestring) != 0 || append_text_local(&rendered, &cap, &used, "\n") != 0) { free(rendered); cJSON_Delete(filtered); return json_error_local("failed to serialize dm history"); } } if (used > 0U && rendered[used - 1] == '\n') { rendered[used - 1] = '\0'; } } else { rendered = cJSON_PrintUnformatted(filtered); if (!rendered) { cJSON_Delete(filtered); return json_error_local("failed to serialize dm history"); } } cJSON_Delete(filtered); cJSON* out = cJSON_CreateObject(); if (!out) { free(rendered); return NULL; } cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "content", rendered); free(rendered); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; } char* execute_agent_version(const char* args_json) { cJSON* args = parse_args_local(args_json); if (!args) return json_error_local("invalid arguments JSON"); cJSON_Delete(args); cJSON* out = cJSON_CreateObject(); if (!out) return NULL; cJSON_AddBoolToObject(out, "success", 1); cJSON_AddStringToObject(out, "name", DIDACTYL_NAME); cJSON_AddStringToObject(out, "description", DIDACTYL_DESCRIPTION); cJSON_AddStringToObject(out, "software", DIDACTYL_SOFTWARE); cJSON_AddStringToObject(out, "version", DIDACTYL_VERSION); cJSON_AddNumberToObject(out, "version_major", DIDACTYL_VERSION_MAJOR); cJSON_AddNumberToObject(out, "version_minor", DIDACTYL_VERSION_MINOR); cJSON_AddNumberToObject(out, "version_patch", DIDACTYL_VERSION_PATCH); char* json = cJSON_PrintUnformatted(out); cJSON_Delete(out); return json; }